เหตุใดผลข้างเคียงจึงถือเป็นความชั่วในการเขียนโปรแกรมเชิงหน้าที่?


69

ฉันรู้สึกว่าผลข้างเคียงเป็นปรากฏการณ์ธรรมชาติ แต่มันเป็นสิ่งที่ต้องห้ามในภาษาที่ใช้งานได้ อะไรคือเหตุผล?

คำถามของฉันเฉพาะกับรูปแบบการเขียนโปรแกรมการทำงาน ไม่ใช่ภาษา / กระบวนทัศน์การเขียนโปรแกรมทั้งหมด


6
โปรแกรมที่ไม่มีผลข้างเคียงนั้นไร้ประโยชน์ดังนั้นผลข้างเคียงจึงไม่ใช่ความชั่วร้ายหรือข้อห้าม แต่ FP encurages การลบโค้ดด้วยผลข้างเคียงดังนั้นส่วนใหญ่ของรหัสที่เป็นไปได้คือฟังก์ชั่นฟรีที่ไม่มีผลข้างเคียง สิ่งนี้ได้รับการสนับสนุนเพราะฟังก์ชั่นและระบบย่อยที่ไม่มีผลข้างเคียงง่ายต่อการเข้าใจง่ายต่อการวิเคราะห์ง่ายต่อการทดสอบและการปรับให้เหมาะสมง่ายขึ้น
JacquesB

@JacquesB มันจะทำให้คำตอบที่ดีในการอธิบายว่าทำไมพวกเขาเข้าใจง่ายขึ้นง่ายต่อการวิเคราะห์ง่ายต่อการทดสอบและเพิ่มประสิทธิภาพง่ายขึ้น
ceving

คำตอบ:


72

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

นอกจากนี้ยังทำให้ง่ายต่อการเขียนฟังก์ชั่นเหล่านั้นเพื่อสร้างพฤติกรรมใหม่

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

แก้ไข: ตามคำร้องขอของ Benjol: เนื่องจากรัฐของคุณเก็บไว้ในสแต็กจำนวนมาก (การไหลของข้อมูลไม่ใช่การควบคุมการไหลตามที่ Jonas เรียกว่าที่นี่ ) คุณสามารถขนานหรือเรียงลำดับการดำเนินการของส่วนต่างๆของการคำนวณของคุณ ซึ่งกันและกัน คุณสามารถค้นหาชิ้นส่วนอิสระเหล่านั้นได้อย่างง่ายดายเพราะส่วนหนึ่งไม่ได้ให้อินพุตกับส่วนอื่น

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


4
อาจลองเพิ่มบางอย่างเกี่ยวกับการทำงานพร้อมกันในคำตอบของคุณ?
Benjol

5
ฟังก์ชันฟรีที่มีผลข้างเคียงนั้นง่ายต่อการทดสอบและนำมาใช้ซ้ำ
LennyProgrammers

@ Lenny222: การนำกลับมาใช้ใหม่เป็นสิ่งที่ฉันบอกใบ้โดยพูดถึงองค์ประกอบของฟังก์ชั่น
Frank Shearar

@ Frank: Ah, ok, ตื้นเกินไปเรียกดู :)
LennyProgrammers

@ Lenny222: ไม่เป็นไร มันอาจเป็นสิ่งที่ดีที่จะสะกดออกมา
Frank Shearar

23

จากบทความเกี่ยวกับฟังก์ชั่นการเขียนโปรแกรม :

ในทางปฏิบัติแอปพลิเคชันต้องมีผลข้างเคียงบางอย่าง Simon Peyton-Jones ผู้สนับสนุนหลักของภาษาโปรแกรมการใช้งาน Haskell กล่าวว่า "ในท้ายที่สุดโปรแกรมใด ๆ จะต้องควบคุมสถานะโปรแกรมที่ไม่มีผลข้างเคียงใด ๆ ที่เป็นกล่องดำสิ่งที่คุณบอกได้คือ กล่องร้อนขึ้น " ( http://oscon.blip.tv/file/324976 ) กุญแจสำคัญคือการ จำกัด ผลข้างเคียงระบุอย่างชัดเจนและหลีกเลี่ยงการกระจายไปทั่วรหัส


2
oscon.blip.tv/file/324976ได้ถูกแทนที่โดยyoutube.com/watch?v=iSmkqocn0oQ&t=3m20s
Gaurav

23

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

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


นี่คือเหตุผลที่พวกเขา "สิ่งที่ต้องห้าม" - FPL สนับสนุนให้คุณ จำกัด ผลข้างเคียง
Frank Shearar

+1 สำหรับวิธีการ ผลข้างเคียงยังคงมีอยู่ แท้จริงแล้วพวกเขาถูก จำกัด
Belun

เพื่อความกระจ่างแจ้งฉันยังไม่ได้พูดว่า 'ทำไมไม่อนุญาตให้ใช้ผลข้างเคียงในการเขียนโปรแกรมการทำงาน' หรือ 'ทำไมไม่ต้องการผลข้างเคียง' ฉันรู้ว่ามันได้รับอนุญาตในภาษาที่ใช้งานได้และบางครั้งก็เป็นสิ่งจำเป็น แต่มันก็เป็นกำลังใจอย่างมากในการเขียนโปรแกรมฟังก์ชั่น ทำไม? นั่นคือคำถามของฉัน
Gulshan

@Gulshan - เพราะผลข้างเคียงทำให้โปรแกรมยากต่อการเข้าใจและปรับให้เหมาะสม
ChaosPandion

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

13

หมายเหตุเล็กน้อย:

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

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


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

1
@NoelWidmer มีบางอย่างที่มีอยู่แล้ว PL / SQL ของ Oracle เสนอส่วนdeterministicคำสั่งสำหรับฟังก์ชันที่ไม่มีผลข้างเคียงดังนั้นจึงไม่ถูกเรียกใช้งานบ่อยเกินความจำเป็น
281377

ว้าว! อย่างไรก็ตามฉันคิดว่าภาษาควรมีความหมายเชิงความหมายเพื่อให้คอมไพเลอร์สามารถเข้าใจได้ด้วยตัวเองโดยไม่ต้องระบุการตั้งค่าสถานะที่ชัดเจน (ฉันไม่แน่ใจว่าประโยคคืออะไร) วิธีการแก้ปัญหาคือการระบุพารามิเตอร์ที่จะไม่แน่นอน / ไม่เปลี่ยนรูป fe โดยทั่วไปการพูดนี้จะต้องมีระบบประเภทที่แข็งแกร่งซึ่งสมมติฐานเกี่ยวกับผลข้างเคียงสามารถทำได้โดยคอมไพเลอร์ และคุณสมบัติจะต้องสามารถปิดได้หากต้องการ เลือกไม่ใช้แทนการเข้าร่วม นั่นเป็นเพียงความคิดของฉันอยู่บนพื้นฐานของความรู้ จำกัด ฉันมีตั้งแต่ผมอ่านคำตอบของคุณ :)
Noel Widmer

deterministicข้อเป็นเพียงคำหลักที่บอกคอมไพเลอร์ที่ว่านี้เป็นฟังก์ชั่นที่กำหนดขึ้นเมื่อเทียบกับวิธีการที่finalคำหลักในชวาบอกคอมไพเลอร์ที่ตัวแปรไม่สามารถเปลี่ยน
281377

11

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

ลองพิจารณาตัวอย่างง่ายๆนี้:

val foo = 42
// Several lines of code you don't really care about, but that contain a
// lot of function calls that use foo and may or may not change its value
// by side effect.

// Code you are troubleshooting
// What's the expected value of foo here?

ในภาษาที่ใช้งานได้ฉันรู้ว่าfooยังคงเป็น 42 ฉันไม่ต้องดูรหัสในระหว่างเข้าใจน้อยลงหรือดูการใช้งานของฟังก์ชันที่เรียกใช้

ทุกอย่างเกี่ยวกับการทำงานพร้อมกันและการขนานและการเพิ่มประสิทธิภาพนั้นดี แต่นั่นคือสิ่งที่นักวิทยาศาสตร์คอมพิวเตอร์ใส่ไว้ในแผ่นพับ ไม่ต้องสงสัยเลยว่าใครกำลังกลายพันธุ์ตัวแปรของคุณและเมื่อไหร่ที่ฉันจะสนุกกับการฝึกฝนแบบวันต่อวัน


6

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

เหตุใดผลข้างเคียงจึงถือเป็นความชั่ว

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

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

ประโยชน์ที่ได้รับ

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

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

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


1
Ada ทำให้มันยากมากที่จะทำให้เกิดผลข้างเคียง มันเป็นไปไม่ได้ แต่คุณรู้อย่างชัดเจนว่าคุณทำอะไร
mouviciel

@mouviciel: ฉันคิดว่ามีภาษาที่มีประโยชน์อย่างน้อยสองสามภาษาที่ทำให้เกิดผลข้างเคียงที่ยากมากและพยายามผลักไสพวกเขาให้ Monads
Merlyn Morgan-Graham

4

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

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

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


+1 - "หรือผู้ร่วมงานที่ไม่สงสัย"
Merlyn Morgan-Graham

1
-1 สำหรับผลข้างเคียงคือ "การรั่วไหลที่ต้องได้รับการจัดการ" การสร้าง "ผลข้างเคียง" (รหัสที่ไม่ได้ใช้งานได้จริง) คือจุดประสงค์ทั้งหมดของการเขียนโปรแกรมคอมพิวเตอร์ที่ไม่สำคัญ
Mason Wheeler

ความคิดเห็นนี้มาหกปีต่อมา แต่มีผลข้างเคียงแล้วมีผลข้างเคียง ผลข้างเคียงที่ต้องการการทำ I / O และอื่น ๆ เป็นสิ่งจำเป็นสำหรับโปรแกรมใด ๆ เพราะคุณต้องให้ผลลัพธ์กับผู้ใช้อย่างใดอย่างหนึ่ง - แต่ผลข้างเคียงชนิดอื่นที่รหัสของคุณเปลี่ยนแปลงโดยไม่ดี เหตุผลเช่นทำ I / O เป็น "รั่ว" แน่นอนที่จะต้องจัดการในภายหลัง แนวคิดพื้นฐานคือการแยกคำสั่งแบบสอบถาม : ฟังก์ชันที่คืนค่าควรไม่มีผลข้างเคียง
rmunn

4

IMHO นี่มันเจ้าเล่ห์มาก ไม่มีใครชอบผลข้างเคียง แต่ทุกคนต้องการมัน

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

กระบวนทัศน์การเขียนโปรแกรมเช่น OO และการทำงานทั้งที่อยู่ปัญหานี้ OO ลดปัญหาโดยกำหนดความกังวลแยก ซึ่งหมายถึงสถานะแอปพลิเคชันซึ่งประกอบด้วยข้อมูลที่ไม่แน่นอนจำนวนมากถูกห่อหุ้มอยู่ในวัตถุซึ่งแต่ละแห่งมีหน้าที่ในการรักษาสถานะของตนเองเท่านั้น วิธีนี้จะช่วยลดความเสี่ยงของการพึ่งพาและปัญหาที่แยกได้มากขึ้นและง่ายต่อการติดตาม

การเขียนโปรแกรมเชิงฟังก์ชั่นนั้นใช้วิธีการที่รุนแรงกว่าซึ่งสถานะของแอพพลิเคชั่นนั้นไม่เปลี่ยนแปลงจากมุมมองของโปรแกรมเมอร์ นี่เป็นความคิดที่ดี แต่ทำให้ภาษาไร้ประโยชน์ในตัวมันเอง ทำไม? เนื่องจากการดำเนินการ I / O ใด ๆ มีผลข้างเคียง ทันทีที่คุณอ่านจากอินพุตสตรีมสถานะแอปพลิเคชันของคุณมีแนวโน้มที่จะเปลี่ยนแปลงเนื่องจากในครั้งต่อไปที่คุณเรียกใช้ฟังก์ชันเดียวกันผลลัพธ์ที่ได้น่าจะแตกต่างกัน คุณอาจกำลังอ่านข้อมูลที่แตกต่างกันหรือ - อาจเป็นไปได้ - การดำเนินการอาจล้มเหลว เช่นเดียวกับผลลัพธ์ เอาท์พุทคือการดำเนินการที่มีผลข้างเคียง นี่คือสิ่งที่คุณไม่ได้ตระหนักถึงทุกวันนี้ แต่คิดว่าคุณมีเพียง 20K สำหรับผลลัพธ์ของคุณและถ้าคุณส่งออกมากกว่านี้แอปของคุณก็จะล่มเพราะคุณไม่มีพื้นที่ดิสก์หรืออะไรก็ตาม

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


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

@Ilan: นี่ก็เป็นจริงสำหรับบางภาษาที่ใช้งานได้และเป็นรูปแบบที่ใช้ง่าย
back2dos

"การเขียนโปรแกรมเชิงฟังก์ชั่นใช้วิธีการที่รุนแรงกว่าซึ่งสถานะของแอปพลิเคชันนั้นไม่เปลี่ยนรูปแบบจากมุมมองของโปรแกรมเมอร์นี่เป็นความคิดที่ดี แต่ทำให้ภาษาไร้ประโยชน์ด้วยตนเองเพราะเหตุใด I / O ผลกระทบ ": FP ไม่ได้ห้ามผลข้างเคียง แต่จะ จำกัด ไว้เมื่อไม่จำเป็น เช่น (1) I / O -> ผลข้างเคียงเป็นสิ่งจำเป็น (2) การคำนวณฟังก์ชั่นรวมจากลำดับของค่า -> ผลข้างเคียง (เช่นสำหรับลูปที่มีตัวแปรสะสม) ไม่จำเป็น
Giorgio

2

ผลข้างเคียงใด ๆ แนะนำพารามิเตอร์อินพุต / เอาต์พุตพิเศษซึ่งจะต้องนำมาพิจารณาเมื่อทำการทดสอบ

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

ด้วยการพยายามหลีกเลี่ยงผลข้างเคียงคุณจะ จำกัด จำนวนของการมองจากภายนอกที่จำเป็นในการเรียกใช้รหัส


1

จากประสบการณ์ของฉันการออกแบบที่ดีในการวางโปรแกรม Object Orientated สั่งการใช้ฟังก์ชันที่มีผลข้างเคียง

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

ห่างไกลจากความชั่วร้ายการจัดเรียงที่ถูกต้องของผลข้างเคียงของการแก้ไขฮีปและการแก้ไขหน้าจอเหล่านี้เป็นหัวใจของการออกแบบ OO (ในกรณีนี้คือรูปแบบ MVC)

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


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

1
@ChrisF ส่วนใหญ่ defintely มันเป็นผลข้างเคียง ข้อความที่ส่งผ่านไปยังผู้สังเกตการณ์ (ใน OO ค่อนข้างเป็นไปได้มากว่าการเรียกใช้เมธอดบนอินเทอร์เฟซ) จะนำไปสู่สถานะขององค์ประกอบ UI ในการเปลี่ยนฮีป (และวัตถุฮีปเหล่านี้จะปรากฏในส่วนอื่น ๆ ของโปรแกรม) องค์ประกอบ UI ไม่ใช่พารามิเตอร์ของวิธีการหรือค่าส่งคืน ในแง่ที่เป็นทางการสำหรับฟังก์ชั่นที่จะปราศจากผลข้างเคียงมันต้องเป็น idempotent การแจ้งเตือนในรูปแบบ MVC ไม่ใช่ตัวอย่างเช่น UI อาจแสดงรายการข้อความที่ได้รับ - คอนโซล - การเรียกมันสองครั้งส่งผลให้สถานะโปรแกรมแตกต่างกัน
flamingpenguin

0

ความชั่วร้ายอยู่เหนือสิ่งอื่นใดทั้งหมดขึ้นอยู่กับบริบทของการใช้ภาษา

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


0

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

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

  • ในรหัสที่มีสถานะที่ไม่แน่นอนเราสามารถจัดการขอบเขตของรัฐในลักษณะที่ทำให้มั่นใจได้ว่ามันไม่สามารถรั่วไหลออกไปนอกฟังก์ชั่นที่กำหนดทำให้เราสามารถรวบรวมขยะโดยไม่ต้องอ้างอิงการนับหรือรูปแบบการทำเครื่องหมายและกวาด แต่ยังต้องแน่ใจว่าไม่มีการอ้างอิงใด ๆ การรับประกันแบบเดียวกันนี้ยังมีประโยชน์ในการรักษาข้อมูลที่มีความอ่อนไหวต่อความเป็นส่วนตัวและอื่น ๆ (สามารถทำได้โดยใช้ ST monad ใน Haskell)
  • เมื่อแก้ไขสถานะที่แชร์ในหลาย ๆ เธรดเราสามารถหลีกเลี่ยงความต้องการล็อคโดยการติดตามการเปลี่ยนแปลงและดำเนินการอัปเดตอะตอมมิกเมื่อสิ้นสุดธุรกรรมหรือย้อนกลับธุรกรรมและทำซ้ำถ้าเธรดอื่นทำการแก้ไขที่ขัดแย้งกัน สิ่งนี้สามารถทำได้เพียงเพราะเราสามารถมั่นใจได้ว่ารหัสไม่มีผลกระทบอื่นใดนอกจากการแก้ไขสถานะ (ซึ่งเราสามารถละทิ้งอย่างมีความสุข) ดำเนินการโดย STM (Software Transactional Memory) monad ใน Haskell
  • เราสามารถติดตามเอฟเฟกต์ของรหัสและกล่องทรายเล็ก ๆ น้อย ๆ กรองผลกระทบใด ๆ ที่อาจต้องดำเนินการเพื่อให้แน่ใจว่าปลอดภัยซึ่งทำให้ผู้ใช้ (ตัวอย่าง) ใส่รหัสที่จะดำเนินการอย่างปลอดภัยบนเว็บไซต์

0

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

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

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

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

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

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

for each pixel in an image:
    make it red

มันค่อนข้างง่ายที่จะให้เหตุผลเกี่ยวกับความถูกต้องของรหัสดังกล่าว แต่ส่วนใหญ่เป็นเพราะผลข้างเคียงมีความสม่ำเสมอและการควบคุมการไหลก็ง่ายมาก แต่สมมุติว่าเรามีรหัสดังนี้:

for each vertex to remove in a mesh:
     start removing vertex from connected edges():
         start removing connected edges from connected faces():
             rebuild connected faces excluding edges to remove():
                  if face has less than 3 edges:
                       remove face
             remove edge
         remove vertex

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

for each vertex to remove:
     mark connected edges
for each marked edge:
     mark connected faces
for each marked face:
     remove marked edges from face
     if num_edges < 3:
          remove face

for each marked edge:
     remove edge
for each vertex to remove:
     remove vertex

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

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


-1

มันไม่ใช่ความชั่วร้าย ความคิดเห็นของฉันมีความจำเป็นที่จะต้องแยกแยะความแตกต่างของฟังก์ชั่นทั้งสองประเภท - โดยมีผลข้างเคียงและไม่มี ฟังก์ชั่นที่ไม่มีผลข้างเคียง: - ส่งคืนค่าเดิมที่มีอาร์กิวเมนต์เหมือนกันเสมอดังนั้นตัวอย่างเช่นฟังก์ชันดังกล่าวที่ไม่มีข้อโต้แย้งใด ๆ - นั่นหมายความว่าลำดับในสิ่งที่ฟังก์ชั่นบางอย่างนั้นเรียกว่าไม่มีบทบาท - จะต้องสามารถเรียกใช้และอาจจะดีบั๊กเพียงอย่างเดียว (!) โดยไม่มีรหัสอื่น และตอนนี้ฮ่า ๆ ดูสิ่งที่ JUnit ทำ ฟังก์ชั่นที่มีผลข้างเคียง: - มี "การรั่วไหล" เรียงลำดับสิ่งที่สามารถเน้นได้โดยอัตโนมัติ - เป็นสิ่งสำคัญมากโดยการดีบักและค้นหาข้อผิดพลาดสิ่งที่มักเกิดจากผลข้างเคียง - ฟังก์ชั่นใด ๆ ที่มีผลข้างเคียงก็มี "ส่วน" ของตัวเองโดยไม่มีผลข้างเคียงสิ่งที่สามารถแยกออกได้โดยอัตโนมัติ ดังนั้นความชั่วร้ายจึงเป็นผลข้างเคียง


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