ฉันรู้สึกว่าผลข้างเคียงเป็นปรากฏการณ์ธรรมชาติ แต่มันเป็นสิ่งที่ต้องห้ามในภาษาที่ใช้งานได้ อะไรคือเหตุผล?
คำถามของฉันเฉพาะกับรูปแบบการเขียนโปรแกรมการทำงาน ไม่ใช่ภาษา / กระบวนทัศน์การเขียนโปรแกรมทั้งหมด
ฉันรู้สึกว่าผลข้างเคียงเป็นปรากฏการณ์ธรรมชาติ แต่มันเป็นสิ่งที่ต้องห้ามในภาษาที่ใช้งานได้ อะไรคือเหตุผล?
คำถามของฉันเฉพาะกับรูปแบบการเขียนโปรแกรมการทำงาน ไม่ใช่ภาษา / กระบวนทัศน์การเขียนโปรแกรมทั้งหมด
คำตอบ:
การเขียนฟังก์ชั่น / วิธีการของคุณโดยไม่มีผลข้างเคียง - ดังนั้นจึงเป็นฟังก์ชั่นที่บริสุทธิ์ - ทำให้ง่ายต่อการให้เหตุผลเกี่ยวกับความถูกต้องของโปรแกรมของคุณ
นอกจากนี้ยังทำให้ง่ายต่อการเขียนฟังก์ชั่นเหล่านั้นเพื่อสร้างพฤติกรรมใหม่
นอกจากนี้ยังทำให้การเพิ่มประสิทธิภาพบางอย่างเป็นไปได้ที่คอมไพเลอร์สามารถเช่นบันทึกผลลัพธ์ของฟังก์ชั่นหรือใช้การกำจัด Subexpression ทั่วไป
แก้ไข: ตามคำร้องขอของ Benjol: เนื่องจากรัฐของคุณเก็บไว้ในสแต็กจำนวนมาก (การไหลของข้อมูลไม่ใช่การควบคุมการไหลตามที่ Jonas เรียกว่าที่นี่ ) คุณสามารถขนานหรือเรียงลำดับการดำเนินการของส่วนต่างๆของการคำนวณของคุณ ซึ่งกันและกัน คุณสามารถค้นหาชิ้นส่วนอิสระเหล่านั้นได้อย่างง่ายดายเพราะส่วนหนึ่งไม่ได้ให้อินพุตกับส่วนอื่น
ในสภาพแวดล้อมที่มี debuggers ที่ให้คุณย้อนกลับกองซ้อนและดำเนินการคำนวณต่อ (เช่น Smalltalk) การมีฟังก์ชั่นที่บริสุทธิ์หมายความว่าคุณสามารถเห็นได้อย่างง่ายดายว่าการเปลี่ยนแปลงของค่านั้นเป็นอย่างไรเนื่องจากสถานะก่อนหน้านี้ ในการคำนวณการกลายพันธุ์อย่างหนักยกเว้นว่าคุณเพิ่มการกระทำที่ต้องทำ / เลิกทำลงในโครงสร้างหรืออัลกอริทึมของคุณอย่างชัดเจนคุณจะไม่เห็นประวัติของการคำนวณ (สิ่งนี้จะสัมพันธ์กับย่อหน้าแรก: การเขียนฟังก์ชั่นล้วนทำให้ง่ายต่อการตรวจสอบความถูกต้องของโปรแกรมของคุณ)
จากบทความเกี่ยวกับฟังก์ชั่นการเขียนโปรแกรม :
ในทางปฏิบัติแอปพลิเคชันต้องมีผลข้างเคียงบางอย่าง Simon Peyton-Jones ผู้สนับสนุนหลักของภาษาโปรแกรมการใช้งาน Haskell กล่าวว่า "ในท้ายที่สุดโปรแกรมใด ๆ จะต้องควบคุมสถานะโปรแกรมที่ไม่มีผลข้างเคียงใด ๆ ที่เป็นกล่องดำสิ่งที่คุณบอกได้คือ กล่องร้อนขึ้น " ( http://oscon.blip.tv/file/324976 ) กุญแจสำคัญคือการ จำกัด ผลข้างเคียงระบุอย่างชัดเจนและหลีกเลี่ยงการกระจายไปทั่วรหัส
คุณได้รับมันผิดการเขียนโปรแกรมการทำงานส่งเสริมข้อ จำกัด ด้านผลข้างเคียงเพื่อให้โปรแกรมเข้าใจง่ายและเพิ่มประสิทธิภาพ แม้แต่ Haskell ยังอนุญาตให้คุณเขียนไฟล์
โดยพื้นฐานแล้วสิ่งที่ฉันพูดคือโปรแกรมเมอร์ที่ทำงานไม่คิดว่าผลข้างเคียงเป็นความชั่วร้ายพวกเขาคิดว่าการ จำกัด การใช้ผลข้างเคียงนั้นดี ฉันรู้ว่ามันอาจดูเหมือนความแตกต่างที่เรียบง่าย
readFile
ทำคือการกำหนดลำดับของการกระทำ ลำดับนี้บริสุทธิ์ตามหน้าที่และเป็นเหมือนต้นไม้นามธรรมที่อธิบายว่าจะต้องทำอะไร ผลข้างเคียงที่สกปรกจริงจะถูกดำเนินการโดยรันไทม์
หมายเหตุเล็กน้อย:
ฟังก์ชั่นที่ไม่มีผลข้างเคียงสามารถดำเนินการได้เล็กน้อยในแบบคู่ขนานในขณะที่ฟังก์ชั่นที่มีผลข้างเคียงมักต้องการการซิงโครไนซ์บางประเภท
ฟังก์ชั่นไม่มีผลข้างเคียงอนุญาตให้มีการเพิ่มประสิทธิภาพเชิงรุกมากขึ้น (เช่น transparentely ใช้แคชผล) เพราะตราบใดที่เราจะได้รับผลที่เหมาะสมก็ไม่ได้เรื่องหรือไม่ว่าฟังก์ชั่นที่ถูกจริงๆดำเนินการ
deterministic
คำสั่งสำหรับฟังก์ชันที่ไม่มีผลข้างเคียงดังนั้นจึงไม่ถูกเรียกใช้งานบ่อยเกินความจำเป็น
deterministic
ข้อเป็นเพียงคำหลักที่บอกคอมไพเลอร์ที่ว่านี้เป็นฟังก์ชั่นที่กำหนดขึ้นเมื่อเทียบกับวิธีการที่final
คำหลักในชวาบอกคอมไพเลอร์ที่ตัวแปรไม่สามารถเปลี่ยน
ฉันทำงานในรหัสการทำงานเป็นหลักในขณะนี้และจากมุมมองที่ดูเหมือนว่าทำให้ไม่เห็นชัดเจน ผลข้างเคียงสร้างภาระทางจิตใจอย่างมากต่อโปรแกรมเมอร์ที่พยายามอ่านและเข้าใจโค้ด คุณไม่ได้สังเกตว่าภาระนั้นจนกว่าคุณจะว่างจากมันไปซักพักแล้วก็ต้องอ่านโค้ดที่มีผลข้างเคียงอีกครั้ง
ลองพิจารณาตัวอย่างง่ายๆนี้:
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 ฉันไม่ต้องดูรหัสในระหว่างเข้าใจน้อยลงหรือดูการใช้งานของฟังก์ชันที่เรียกใช้
ทุกอย่างเกี่ยวกับการทำงานพร้อมกันและการขนานและการเพิ่มประสิทธิภาพนั้นดี แต่นั่นคือสิ่งที่นักวิทยาศาสตร์คอมพิวเตอร์ใส่ไว้ในแผ่นพับ ไม่ต้องสงสัยเลยว่าใครกำลังกลายพันธุ์ตัวแปรของคุณและเมื่อไหร่ที่ฉันจะสนุกกับการฝึกฝนแบบวันต่อวัน
ไม่กี่ภาษาทำให้ไม่สามารถทำให้เกิดผลข้างเคียงได้ ภาษาที่ปราศจากผลข้างเคียงโดยสมบูรณ์นั้นยากที่จะใช้งาน (ใกล้ถึงเป็นไปไม่ได้) ยกเว้นในพื้นที่ที่ จำกัด มาก
เหตุใดผลข้างเคียงจึงถือเป็นความชั่ว
เพราะพวกเขาทำให้มันยากมากที่จะให้เหตุผลเกี่ยวกับสิ่งที่โปรแกรมทำและเพื่อพิสูจน์ว่ามันทำในสิ่งที่คุณคาดหวังว่าจะทำ
ในระดับที่สูงมากลองนึกภาพการทดสอบเว็บไซต์ทั้ง 3 ชั้นด้วยการทดสอบแบบกล่องดำเท่านั้น แน่นอนมันทำได้ขึ้นอยู่กับขนาด แต่มีการทำสำเนาเกิดขึ้นมากมายอย่างแน่นอน และถ้ามีเป็นข้อผิดพลาด (ที่เกี่ยวข้องกับผลข้างเคียง) แล้วคุณอาจจะทำลายทั้งระบบสำหรับการทดสอบต่อไปจนกว่าจะมีข้อผิดพลาดคือการวินิจฉัยและการแก้ไขและการแก้ไขจะนำไปใช้กับสภาพแวดล้อมการทดสอบ
ประโยชน์ที่ได้รับ
ตอนนี้ลดขนาดลง หากคุณเขียนโค้ดฟรีผลข้างเคียงได้ค่อนข้างดีคุณจะให้เหตุผลว่าโค้ดที่มีอยู่เดิมนั้นเร็วแค่ไหน? คุณเขียนการทดสอบหน่วยเร็วขึ้นเท่าไหร่ วิธีมั่นใจว่าคุณจะรู้สึกว่ารหัสที่ไม่มีผลข้างเคียงที่ได้รับการรับประกันข้อผิดพลาดฟรีและให้ผู้ใช้สามารถ จำกัด การสัมผัสของพวกเขาให้ข้อบกพร่องใด ๆ มันไม่ได้?
หากโค้ดไม่มีผลข้างเคียงคอมไพเลอร์อาจมีการปรับแต่งเพิ่มเติมที่สามารถทำได้ อาจง่ายกว่ามากที่จะใช้การปรับให้เหมาะสมเหล่านั้น อาจเป็นการง่ายกว่าที่จะคิดการเพิ่มประสิทธิภาพสำหรับรหัสฟรีผลข้างเคียงซึ่งหมายความว่าผู้จำหน่ายคอมไพเลอร์ของคุณอาจใช้การเพิ่มประสิทธิภาพที่ยากต่อการเป็นไปไม่ได้ในรหัสที่มีผลข้างเคียง
การทำงานพร้อมกันนั้นง่ายกว่ามากในการใช้สร้างโดยอัตโนมัติและเพิ่มประสิทธิภาพเมื่อโค้ดไม่มีผลข้างเคียง นี่เป็นเพราะชิ้นส่วนทั้งหมดสามารถประเมินได้อย่างปลอดภัยในลำดับใด ๆ ช่วยให้โปรแกรมเมอร์เขียนโค้ดพร้อมกันสูงถือว่าเป็นความท้าทายที่ใหญ่ต่อไปว่าวิทยาการคอมพิวเตอร์ความต้องการที่จะแก้ไขปัญหาและเป็นหนึ่งในการป้องกันความเสี่ยงที่เหลืออยู่ไม่กี่กับกฎของมัวร์
ผลข้างเคียงเป็นเหมือน "รอยรั่ว" ในรหัสของคุณซึ่งจะต้องได้รับการจัดการในภายหลังโดยคุณหรือเพื่อนร่วมงานที่ไม่ไว้วางใจ
ภาษาที่ใช้งานได้จะหลีกเลี่ยงตัวแปรสถานะและข้อมูลที่ไม่สามารถเปลี่ยนแปลงได้ซึ่งเป็นวิธีการสร้างโค้ดที่ขึ้นกับบริบทน้อยลงและเป็นแบบแยกส่วนมากขึ้น Modularity มั่นใจได้ว่าการทำงานของนักพัฒนารายหนึ่งจะไม่ส่งผลกระทบ / บ่อนทำลายการทำงานของผู้อื่น
การปรับอัตราการพัฒนาด้วยขนาดของทีมเป็น "จอกศักดิ์สิทธิ์" ของการพัฒนาซอฟต์แวร์ในปัจจุบัน เมื่อทำงานกับโปรแกรมเมอร์อื่น ๆ มีบางสิ่งที่สำคัญพอ ๆ กับโมดูล แม้แต่ผลข้างเคียงที่ง่ายที่สุดของตรรกะก็ทำให้การทำงานร่วมกันเป็นไปได้ยากมาก
IMHO นี่มันเจ้าเล่ห์มาก ไม่มีใครชอบผลข้างเคียง แต่ทุกคนต้องการมัน
สิ่งที่เป็นอันตรายเกี่ยวกับผลข้างเคียงคือถ้าคุณเรียกใช้ฟังก์ชั่นสิ่งนี้อาจมีผลกระทบไม่เพียง แต่ในลักษณะที่ฟังก์ชั่นทำงานเมื่อมันถูกเรียกในครั้งต่อไป แต่อาจมีผลต่อฟังก์ชันอื่น ๆ ดังนั้นผลข้างเคียงจึงทำให้เกิดพฤติกรรมที่คาดเดาไม่ได้และการพึ่งพาที่ไม่เกี่ยวข้อง
กระบวนทัศน์การเขียนโปรแกรมเช่น OO และการทำงานทั้งที่อยู่ปัญหานี้ OO ลดปัญหาโดยกำหนดความกังวลแยก ซึ่งหมายถึงสถานะแอปพลิเคชันซึ่งประกอบด้วยข้อมูลที่ไม่แน่นอนจำนวนมากถูกห่อหุ้มอยู่ในวัตถุซึ่งแต่ละแห่งมีหน้าที่ในการรักษาสถานะของตนเองเท่านั้น วิธีนี้จะช่วยลดความเสี่ยงของการพึ่งพาและปัญหาที่แยกได้มากขึ้นและง่ายต่อการติดตาม
การเขียนโปรแกรมเชิงฟังก์ชั่นนั้นใช้วิธีการที่รุนแรงกว่าซึ่งสถานะของแอพพลิเคชั่นนั้นไม่เปลี่ยนแปลงจากมุมมองของโปรแกรมเมอร์ นี่เป็นความคิดที่ดี แต่ทำให้ภาษาไร้ประโยชน์ในตัวมันเอง ทำไม? เนื่องจากการดำเนินการ I / O ใด ๆ มีผลข้างเคียง ทันทีที่คุณอ่านจากอินพุตสตรีมสถานะแอปพลิเคชันของคุณมีแนวโน้มที่จะเปลี่ยนแปลงเนื่องจากในครั้งต่อไปที่คุณเรียกใช้ฟังก์ชันเดียวกันผลลัพธ์ที่ได้น่าจะแตกต่างกัน คุณอาจกำลังอ่านข้อมูลที่แตกต่างกันหรือ - อาจเป็นไปได้ - การดำเนินการอาจล้มเหลว เช่นเดียวกับผลลัพธ์ เอาท์พุทคือการดำเนินการที่มีผลข้างเคียง นี่คือสิ่งที่คุณไม่ได้ตระหนักถึงทุกวันนี้ แต่คิดว่าคุณมีเพียง 20K สำหรับผลลัพธ์ของคุณและถ้าคุณส่งออกมากกว่านี้แอปของคุณก็จะล่มเพราะคุณไม่มีพื้นที่ดิสก์หรืออะไรก็ตาม
ใช่ผลข้างเคียงน่ารังเกียจและอันตรายจากมุมมองของโปรแกรมเมอร์ ข้อบกพร่องส่วนใหญ่มาจากวิธีที่บางส่วนของสถานะแอปพลิเคชันถูกเชื่อมโยงกันในลักษณะที่คลุมเครือเกือบจะผ่านผลข้างเคียงที่ไม่ได้รับการพิจารณาและบ่อยครั้ง จากมุมมองของผู้ใช้ผลข้างเคียงเป็นจุดของการใช้คอมพิวเตอร์ พวกเขาไม่สนใจสิ่งที่เกิดขึ้นภายในหรือวิธีการจัดระเบียบ พวกเขาทำอะไรบางอย่างและคาดว่าคอมพิวเตอร์จะเปลี่ยนตาม
ผลข้างเคียงใด ๆ แนะนำพารามิเตอร์อินพุต / เอาต์พุตพิเศษซึ่งจะต้องนำมาพิจารณาเมื่อทำการทดสอบ
สิ่งนี้ทำให้การตรวจสอบความถูกต้องของรหัสมีความซับซ้อนมากขึ้นเนื่องจากสภาพแวดล้อมไม่สามารถ จำกัด ได้เพียงแค่การตรวจสอบความถูกต้องของรหัส แต่จะต้องนำสภาพแวดล้อมโดยรอบบางส่วนหรือทั้งหมด (ทั่วโลกที่มีการปรับปรุงชีวิตในรหัสนั้น รหัสซึ่งจะขึ้นอยู่กับการใช้งานภายในเซิร์ฟเวอร์ Java EE แบบเต็ม .... )
ด้วยการพยายามหลีกเลี่ยงผลข้างเคียงคุณจะ จำกัด จำนวนของการมองจากภายนอกที่จำเป็นในการเรียกใช้รหัส
จากประสบการณ์ของฉันการออกแบบที่ดีในการวางโปรแกรม Object Orientated สั่งการใช้ฟังก์ชันที่มีผลข้างเคียง
ตัวอย่างเช่นใช้แอปพลิเคชัน UI พื้นฐาน ฉันอาจมีโปรแกรมที่กำลังทำงานซึ่งมีกราฟวัตถุแสดงถึงสถานะปัจจุบันของโมเดลโดเมนของโปรแกรมของฉัน ข้อความมาถึงวัตถุในกราฟนั้น (เช่นผ่านวิธีการเรียกใช้จากตัวควบคุมเลเยอร์ UI) กราฟวัตถุ (โมเดลโดเมน) บนฮีปมีการปรับเปลี่ยนเพื่อตอบสนองต่อข้อความ ผู้สังเกตการณ์ของโมเดลจะได้รับแจ้งการเปลี่ยนแปลง UI และทรัพยากรอื่น ๆ อาจได้รับการแก้ไข
ห่างไกลจากความชั่วร้ายการจัดเรียงที่ถูกต้องของผลข้างเคียงของการแก้ไขฮีปและการแก้ไขหน้าจอเหล่านี้เป็นหัวใจของการออกแบบ OO (ในกรณีนี้คือรูปแบบ MVC)
แน่นอนว่านั่นไม่ได้หมายความว่าวิธีการของคุณควรมีผลข้างเคียงโดยเจตนา และฟังก์ชั่นฟรีที่ไม่มีผลข้างเคียงมีส่วนช่วยในการปรับปรุงความสามารถในการอ่านได้และบางครั้งประสิทธิภาพของโค้ดของคุณ
ความชั่วร้ายอยู่เหนือสิ่งอื่นใดทั้งหมดขึ้นอยู่กับบริบทของการใช้ภาษา
การพิจารณาถึงสิ่งที่กล่าวถึงไปแล้วก็คือมันทำให้การพิสูจน์ความถูกต้องของโปรแกรมง่ายขึ้นมากหากไม่มีผลข้างเคียงที่ใช้งานได้
ดังที่คำถามข้างต้นได้ชี้ให้เห็นแล้วภาษาที่ใช้งานได้นั้นไม่ค่อยมีการป้องกันโค้ดจากการมีผลข้างเคียงเนื่องจากเรามีเครื่องมือสำหรับการจัดการผลข้างเคียงที่อาจเกิดขึ้นในโค้ดที่กำหนดและเมื่อใด
สิ่งนี้กลับกลายเป็นว่ามีผลที่น่าสนใจมาก ข้อแรกและที่เห็นได้ชัดที่สุดมีหลายสิ่งหลายอย่างที่คุณสามารถทำได้ด้วยโค้ดด้านข้างฟรีซึ่งได้อธิบายไปแล้ว แต่มีสิ่งอื่น ๆ ที่เราสามารถทำได้เช่นกันแม้เมื่อทำงานกับรหัสที่มีผลข้างเคียง:
ในฐานรหัสที่ซับซ้อนการโต้ตอบที่ซับซ้อนของผลข้างเคียงเป็นสิ่งที่ยากที่สุดที่ฉันจะหาเหตุผล ฉันพูดได้เป็นการส่วนตัวเท่านั้นเมื่อสมองของฉันทำงาน ผลข้างเคียงและสถานะถาวรและการกลายพันธุ์ของอินพุตและอื่น ๆ ทำให้ฉันต้องคิดเกี่ยวกับ "เมื่อ" และ "ที่" สิ่งต่าง ๆ เกิดขึ้นกับเหตุผลเกี่ยวกับความถูกต้องไม่ใช่แค่ "อะไร" ที่เกิดขึ้นในแต่ละฟังก์ชัน
ฉันไม่สามารถมุ่งเน้นไปที่ "อะไร" ฉันไม่สามารถสรุปได้หลังจากทดสอบฟังก์ชั่นอย่างละเอียดซึ่งทำให้เกิดผลข้างเคียงที่จะแพร่กระจายความน่าเชื่อถือตลอดการใช้รหัสเนื่องจากผู้โทรอาจยังคงใช้งานผิดโดยการโทรในเวลาที่ไม่ถูกต้องจากเธรดที่ไม่ถูกต้อง ใบสั่ง. ในขณะที่ฟังก์ชั่นที่ไม่ก่อให้เกิดผลข้างเคียงและเพียงแค่ส่งคืนเอาต์พุตใหม่เนื่องจากอินพุต (โดยไม่ต้องสัมผัสอินพุต) นั้นเป็นไปไม่ได้ที่จะใช้วิธีนี้
แต่ฉันเป็นคนประเภทจริงจังฉันคิดว่าหรืออย่างน้อยก็พยายามและฉันไม่คิดว่าเราจำเป็นต้องประทับตราผลข้างเคียงทั้งหมดให้น้อยที่สุดเพื่อให้เหตุผลเกี่ยวกับความถูกต้องของรหัสของเรา (อย่างน้อยที่สุด ฉันพบว่ามันยากมากที่จะทำในภาษาเช่น 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) แต่ที่สำคัญที่สุดฉันพบรุ่นที่สองง่ายกว่ามากที่จะให้เหตุผลในแง่ของความถูกต้องรวมทั้งการเปลี่ยนแปลงโดยไม่ทำให้เกิดข้อบกพร่อง ดังนั้น'
และในที่สุดเราก็ต้องการผลข้างเคียงที่จะเกิดขึ้นในบางจุดหรืออย่างอื่นเราก็แค่มีฟังก์ชั่นที่เอาท์พุทข้อมูลที่ไม่มีไปไหน บ่อยครั้งที่เราจำเป็นต้องบันทึกบางสิ่งบางอย่างไปยังไฟล์แสดงบางอย่างไปยังหน้าจอส่งข้อมูลผ่านซ็อกเก็ตบางอย่างของประเภทนี้และสิ่งเหล่านี้ทั้งหมดเป็นผลข้างเคียง แต่เราสามารถลดจำนวนผลข้างเคียงที่เกินจริงที่เกิดขึ้นได้และลดจำนวนผลข้างเคียงที่เกิดขึ้นเมื่อการควบคุมการไหลซับซ้อนมากและฉันคิดว่ามันง่ายกว่ามากหากหลีกเลี่ยงข้อผิดพลาด
มันไม่ใช่ความชั่วร้าย ความคิดเห็นของฉันมีความจำเป็นที่จะต้องแยกแยะความแตกต่างของฟังก์ชั่นทั้งสองประเภท - โดยมีผลข้างเคียงและไม่มี ฟังก์ชั่นที่ไม่มีผลข้างเคียง: - ส่งคืนค่าเดิมที่มีอาร์กิวเมนต์เหมือนกันเสมอดังนั้นตัวอย่างเช่นฟังก์ชันดังกล่าวที่ไม่มีข้อโต้แย้งใด ๆ - นั่นหมายความว่าลำดับในสิ่งที่ฟังก์ชั่นบางอย่างนั้นเรียกว่าไม่มีบทบาท - จะต้องสามารถเรียกใช้และอาจจะดีบั๊กเพียงอย่างเดียว (!) โดยไม่มีรหัสอื่น และตอนนี้ฮ่า ๆ ดูสิ่งที่ JUnit ทำ ฟังก์ชั่นที่มีผลข้างเคียง: - มี "การรั่วไหล" เรียงลำดับสิ่งที่สามารถเน้นได้โดยอัตโนมัติ - เป็นสิ่งสำคัญมากโดยการดีบักและค้นหาข้อผิดพลาดสิ่งที่มักเกิดจากผลข้างเคียง - ฟังก์ชั่นใด ๆ ที่มีผลข้างเคียงก็มี "ส่วน" ของตัวเองโดยไม่มีผลข้างเคียงสิ่งที่สามารถแยกออกได้โดยอัตโนมัติ ดังนั้นความชั่วร้ายจึงเป็นผลข้างเคียง