เหตุใดนักพัฒนาซอฟต์แวร์หลายคนจึงละเมิดหลักการเปิด / ปิด


74

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

คำถามนี้ข้ามไปที่หัวของฉันหลังจากเวอร์ชันที่รวดเร็วและต่อเนื่องในไลบรารีReact

ทุกช่วงเวลาสั้น ๆ ฉันสังเกตเห็นการเปลี่ยนแปลงมากมายในไวยากรณ์ชื่อส่วนประกอบ ... ฯลฯ

ตัวอย่างในReact รุ่นที่กำลังมา :

คำเตือนการเลิกใช้ใหม่

การเปลี่ยนแปลงที่ยิ่งใหญ่ที่สุดคือเราได้ดึง React.PropTypes และ React.createClass ลงในแพ็คเกจของตัวเองแล้ว ทั้งสองยังสามารถเข้าถึงได้ผ่านวัตถุหลักปฏิกิริยา แต่การใช้อย่างใดอย่างหนึ่งจะบันทึกคำเตือนการคัดค้านเพียงครั้งเดียวไปยังคอนโซลเมื่ออยู่ในโหมดการพัฒนา นี่จะเป็นการเปิดใช้งานการปรับขนาดโค้ดในอนาคต

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


  • การเปลี่ยนแปลงเหล่านี้ถือเป็นการละเมิดหลักการนั้นหรือไม่?
  • ในฐานะผู้เริ่มต้นสำหรับบางอย่างเช่นReactฉันจะเรียนรู้ได้อย่างไรด้วยการเปลี่ยนแปลงอย่างรวดเร็วในไลบรารี (มันน่าผิดหวังมาก)

6
นี่เป็นตัวอย่างของการสังเกตอย่างชัดเจนและการเรียกร้องของคุณ 'มากมาย' นั้นไม่แน่นอน โครงการ Lucene และ RichFaces เป็นตัวอย่างที่มีชื่อเสียงและ Windows COMM พอร์ต API แต่ฉันไม่สามารถคิดถึงคนอื่นได้ทัน React เป็นผู้พัฒนาซอฟต์แวร์รายใหญ่หรือไม่?
user207421

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

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

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

4
เพราะมี "หลักการ" และศูนย์ "รูปแบบการออกแบบ" ที่ใช้งานได้จริงในชีวิตจริง 100%
Matti Virkkunen

คำตอบ:


148

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

ดังที่Jörg W Mittag พูดถึงในความคิดเห็นของเขาแล้วหลักการไม่ได้กล่าวว่า "คุณไม่สามารถปรับเปลี่ยนพฤติกรรมขององค์ประกอบได้" - กล่าวว่าเราควรพยายามออกแบบส่วนประกอบในวิธีที่พวกเขาเปิดเพื่อนำผึ้งกลับมาใช้ใหม่ (หรือขยาย) ในหลายวิธีโดยไม่จำเป็นต้องดัดแปลง สิ่งนี้สามารถทำได้โดยการระบุ "จุดขยาย" หรือ @AntP ที่ถูกต้อง "โดยการแยกโครงสร้างคลาส / ฟังก์ชั่นไปยังจุดที่ทุกจุดขยายธรรมชาติมีอยู่โดยค่าเริ่มต้น" IMHO ที่ติดตาม OCP นั้นไม่มีอะไรเหมือนกันกับ"การรักษาเวอร์ชั่นเก่าไว้โดยไม่เปลี่ยนแปลงเพื่อความเข้ากันได้ย้อนหลัง" ! หรืออ้างถึงความคิดเห็นของ @ DerekElkin ด้านล่าง:

OCP เป็นคำแนะนำเกี่ยวกับวิธีการเขียนโมดูล [... ] ไม่ใช่เกี่ยวกับการใช้กระบวนการจัดการการเปลี่ยนแปลงที่ไม่อนุญาตให้เปลี่ยนโมดูล

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


14
IMO เหตุผลที่ดีที่สุดในการ "ละเมิด" OCP คือว่ามันจะใช้เวลามากของความพยายามเพื่อให้สอดคล้องกับมันอย่างถูกต้อง Eric Lippert มีการโพสต์บล็อกที่ยอดเยี่ยมเกี่ยวกับสาเหตุที่คลาส NET Framework หลาย ๆ คลาสดูเหมือนละเมิด OCP
BJ Myers

2
@BJMyers: ขอบคุณสำหรับลิงค์ Jon Skeet มีโพสต์ที่ยอดเยี่ยมเกี่ยวกับ OCP เนื่องจากมีความคล้ายคลึงกับแนวคิดของรูปแบบที่มีการป้องกัน
Doc Brown

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

7
@CandiedOrange: ขอบคุณสำหรับความคิดเห็นของคุณ ฉันไม่เห็นการปรับโครงสร้างและ OCP ตรงกันข้ามกับที่คุณอธิบาย ในการเขียนส่วนประกอบที่เป็นไปตาม OCP นั้นมักจะต้องทำการรีแฟคเตอร์หลายรอบ เป้าหมายควรเป็นองค์ประกอบที่ไม่จำเป็นต้องมีการแก้ไขเพื่อแก้ไข "ความต้องการ" ทั้งครอบครัว อย่างไรก็ตามหนึ่งไม่ควรเพิ่มจุดส่วนขยายโดยพลการไปยังองค์ประกอบ "ในกรณี" ที่นำไปสู่การ overengineering ง่ายเกินไป การใช้ความเป็นไปได้ของการปรับสภาพอาจเป็นทางเลือกที่ดีกว่าในหลายกรณี
Doc Brown

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

67

หลักการเปิด / ปิดมีประโยชน์ แต่ก็มีข้อเสียร้ายแรง

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

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

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

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

ทำอะไรบางอย่างเช่นกรอบงาน. NET - มันยังคงมีอยู่ในชุดคลาสคอลเลกชันที่ได้รับการออกแบบก่อนที่ข้อมูลทั่วไปถูกนำมาใช้มากกว่าทศวรรษที่ผ่านมา แน่นอนว่านี่เป็นสิ่งที่ดีสำหรับการทำงานร่วมกันได้ย้อนหลัง (คุณสามารถอัพเกรดเฟรมได้โดยไม่ต้องเขียนอะไรใหม่) แต่มันยังขยายเฟรมเวิร์กและนำเสนอนักพัฒนาด้วยตัวเลือกชุดใหญ่ที่หลายคนล้าสมัย

เห็นได้ชัดว่าผู้พัฒนา React รู้สึกว่ามันไม่คุ้มกับค่าใช้จ่ายในความซับซ้อนและการขยายโค้ดเพื่อปฏิบัติตามหลักการเปิด / ปิดอย่างเคร่งครัด

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

(การตีความของฉันของหลักการขึ้นอยู่กับหลักการOpen-Closedโดย Robert C. Martin)


37
"โดยหลักการแล้วบอกว่าคุณไม่สามารถปรับเปลี่ยนพฤติกรรมของส่วนประกอบได้ แต่คุณต้องเตรียมชุดตัวเลือกใหม่ที่มีลักษณะการทำงานที่ต้องการและเก็บเวอร์ชันเก่าไว้โดยไม่เปลี่ยนแปลงเพื่อให้เข้ากันได้กับรุ่นเก่า" - ฉันไม่เห็นด้วยกับสิ่งนี้ หลักการบอกว่าคุณควรออกแบบส่วนประกอบในลักษณะที่ไม่ควรเปลี่ยนพฤติกรรมเพราะคุณสามารถขยายมันเพื่อทำสิ่งที่คุณต้องการ ปัญหาคือเรายังไม่ทราบวิธีการทำเช่นนั้นโดยเฉพาะอย่างยิ่งภาษาที่ใช้กันอย่างแพร่หลายในปัจจุบัน ปัญหาการแสดงออกเป็นส่วนหนึ่งของ ...
Jörg W Mittag

8
ตัวอย่างเช่น ... ทั้ง Java และC♯ไม่มีวิธีแก้ปัญหาสำหรับ Expression Haskell และ Scala ทำ แต่ฐานผู้ใช้ของพวกเขามีขนาดเล็กมาก
Jörg W Mittag

1
@Giorgio: ใน Haskell การแก้ปัญหาคือการเรียนประเภท ในสกาลาการแก้ปัญหาคือนัยและวัตถุ ขออภัยฉันไม่มีลิงค์อยู่ในขณะนี้ ใช่หลายวิธี (ที่จริงแล้วพวกเขาไม่จำเป็นต้องเป็น "หลายคน" แต่เป็นวิธีการ "เปิด" ของวิธีการ Lisp ที่จำเป็น) แต่ก็เป็นวิธีที่เป็นไปได้เช่นกัน โปรดทราบว่ามีหลาย phrasings ของปัญหาการแสดงออกเพราะโดยปกติเอกสารจะถูกเขียนในลักษณะที่ผู้เขียนจะเพิ่มข้อ จำกัด ในการแก้ไขปัญหาการแสดงออกซึ่งส่งผลให้ในความเป็นจริงที่ว่าทุกโซลูชั่นที่มีอยู่ในขณะนี้กลายเป็นที่ไม่ถูกต้องแล้วแสดงให้เห็นว่าเขาเอง ...
Jörg W Mittag

1
... ภาษาสามารถแก้ไขปัญหา "ยากขึ้น" ได้ ตัวอย่างเช่น Wadler ใช้ถ้อยคำปัญหานิพจน์ไม่เพียง แต่เกี่ยวกับส่วนขยายแบบแยกส่วนเท่านั้น มัลติวิธี Lisp ทั่วไปอย่างไรก็ตามไม่ปลอดภัยแบบคงที่พวกเขาจะปลอดภัยแบบไดนามิกเท่านั้น Odersky เสริมความแข็งแกร่งให้มากขึ้นด้วยการบอกว่ามันควรจะเป็นแบบแยกส่วนที่ปลอดภัยแบบคงที่นั่นคือความปลอดภัยควรตรวจสอบได้แบบคงที่โดยไม่ต้องดูโปรแกรมทั้งหมดเพียงแค่ดูที่โมดูลเสริม สิ่งนี้ไม่สามารถทำได้กับคลาสประเภท Haskell แต่สามารถทำได้ด้วย Scala และใน…
Jörg W Mittag

2
@Giorgio: แน่นอน สิ่งที่ทำให้ Multimethods Common Lisp แก้ปัญหา EP นั้นจริง ๆ แล้วไม่ใช่การแจกจ่ายหลายอย่าง มันเป็นความจริงที่ว่าวิธีการที่เปิดอยู่ ใน FP ทั่วไป (หรือการโปรแกรมเชิงโพรซีเดอร์) การเลือกปฏิบัติประเภทจะเชื่อมโยงกับฟังก์ชัน ใน OO ทั่วไปวิธีการจะเชื่อมโยงกับประเภท วิธีการเสียงกระเพื่อมสามัญเปิดอยู่พวกเขาสามารถเพิ่มลงในคลาสหลังจากข้อเท็จจริงและในโมดูลอื่น นั่นเป็นคุณสมบัติที่ทำให้สามารถใช้งานได้สำหรับการแก้ไข EP ตัวอย่างเช่นโปรโตคอลของ Clojure เป็นเพียงการแจกจ่ายเพียงอย่างเดียว แต่ยังช่วยแก้ปัญหา EP (ตราบใดที่คุณไม่ยืนยันเรื่องความปลอดภัยคงที่)
Jörg W Mittag

20

ฉันจะเรียกว่าหลักการเปิด / ปิดในอุดมคติ เช่นเดียวกับอุดมคติทั้งหมดมันให้ความสำคัญกับการพัฒนาซอฟต์แวร์น้อยมาก เช่นเดียวกับอุดมคติทุกประการมันเป็นไปไม่ได้ที่จะบรรลุในทางปฏิบัติจริง ๆ - เพียงแค่มุ่งมั่นที่จะเข้าใกล้อุดมคติที่ดีที่สุดเท่าที่จะทำได้

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

ตัวอย่างที่มีชื่อเสียงของสิ่งนี้พบได้ในตัวจัดการหน่วยความจำ Windows 95 ในฐานะส่วนหนึ่งของการตลาดสำหรับ Windows 95 มีการระบุว่าแอปพลิเคชัน Windows 3.1 ทั้งหมดจะทำงานใน Windows 95 Microsoft ได้รับสิทธิ์การใช้งานจริงสำหรับโปรแกรมหลายพันโปรแกรมเพื่อทดสอบใน Windows 95 กรณีปัญหาหนึ่งคือ Sim City Sim City มีข้อผิดพลาดซึ่งทำให้เขียนหน่วยความจำไม่ได้ปันส่วน ใน Windows 3.1 โดยไม่มีตัวจัดการหน่วยความจำ "เหมาะสม" นี่เป็นความผิดพลาดเล็กน้อย อย่างไรก็ตามใน Windows 95 ตัวจัดการหน่วยความจำจะตรวจจับสิ่งนี้และทำให้เกิดความผิดพลาดในการแบ่งส่วน การแก้ไขปัญหา? ใน Windows 95 หากชื่อแอปพลิเคชันของคุณเป็นsimcity.exeจริง OS จะคลายข้อ จำกัด ของตัวจัดการหน่วยความจำเพื่อป้องกันความผิดพลาดในการแบ่งกลุ่ม!

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

ซอฟต์แวร์ส่วนใหญ่ในปัจจุบันโดยเฉพาะโอเพนซอร์ซเป็นไปตามเวอร์ชันผ่อนคลายทั่วไปของหลักการเปิด / ปิด เป็นเรื่องธรรมดามากที่จะเห็นเปิด / ปิดตามมาอย่างไม่น่าเชื่อสำหรับรุ่นเล็ก ๆ น้อย ๆ แต่ถูกปล่อยปละละเลยสำหรับรุ่นใหญ่ ตัวอย่างเช่น Python 2.7 มี "ตัวเลือกที่ไม่ดี" มากมายจาก Python 2.0 และ 2.1 วัน แต่ Python 3.0 กวาดพวกเขาทั้งหมดออกไป (นอกจากนี้การเปลี่ยนจาก Windows 95 codebase กับ codebase Windows NT เมื่อพวกเขาเปิดตัว Windows 2000 ยากจนทุกประเภทของสิ่ง แต่มันไม่ได้หมายความว่าเราไม่เคยมีการจัดการกับผู้จัดการหน่วยความจำการตรวจสอบชื่อโปรแกรมที่จะตัดสินใจพฤติกรรม!)


นั่นเป็นเรื่องราวที่ยอดเยี่ยมเกี่ยวกับ SimCity คุณมีแหล่งที่มาหรือไม่?
BJ Myers

5
@BJMyers มันเป็นเรื่องเก่าโจเอล Spoleky กล่าวถึงมันใกล้ถึงจุดสิ้นสุดของบทความนี้ ฉันอ่านมันเป็นส่วนหนึ่งของหนังสือเกี่ยวกับการพัฒนาวิดีโอเกมเมื่อหลายปีก่อน
Cort Ammon

1
@BJMyers: ฉันค่อนข้างแน่ใจว่าพวกเขามีความเข้ากันได้คล้ายกัน "แฮ็ก" สำหรับการใช้งานที่นิยมหลายสิบ
Doc Brown

3
@BJMyers มีสิ่งต่าง ๆ มากมายเช่นนี้ถ้าคุณต้องการอ่านดีไปที่บล็อกเก่าสิ่งใหม่โดยเรย์มอนด์เฉินเรียกดูประวัติแท็กหรือค้นหา "ความเข้ากันได้" มีความทรงจำของนิทานมากมายรวมถึงบางสิ่งที่เด่นชัดใกล้กับกรณี SimCity ดังกล่าว - Addentum: เฉินไม่ชอบที่จะเรียกชื่อที่จะตำหนิ
Theraot

2
สิ่งเล็ก ๆ น้อย ๆ ก็พังได้แม้ในช่วงเปลี่ยนผ่าน 95-> NT SimCity ดั้งเดิมสำหรับ Windows ยังคงใช้งานได้ดีบน Windows 10 (32 บิต) แม้แต่เกมของ DOS ก็ยังทำงานได้อย่างสมบูรณ์หากคุณปิดเสียงหรือใช้งาน VDMSound เพื่อให้ระบบย่อยของคอนโซลสามารถจัดการกับเสียงได้อย่างถูกต้อง Microsoft ให้ความสำคัญกับความเข้ากันได้ย้อนหลังอย่างมากและพวกเขาไม่ใช้ทางลัด "ลองใส่ไว้ในเครื่องเสมือน" บางครั้งต้องมีวิธีแก้ไขปัญหา แต่ก็ยังค่อนข้างน่าประทับใจโดยเฉพาะอย่างยิ่งในแง่ที่เกี่ยวข้อง
Luaan

11

คำตอบของ Doc Brown นั้นใกล้เคียงกับความถูกต้องส่วนคำตอบอื่น ๆ แสดงให้เห็นถึงความเข้าใจผิดของหลักการ Open Open

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

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

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

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

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

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

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


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

1
@JacquesB ผู้คนและการเปลี่ยนรหัสไม่ได้ละเมิด OCP ส่วนประกอบ (เช่นรหัสจริง) (และเพื่อให้ชัดเจนอย่างสมบูรณ์นั่นหมายความว่าองค์ประกอบไม่สามารถใช้งานได้ถึง OCP เองไม่ใช่ว่าเป็นการละเมิด OCP ของส่วนประกอบอื่น ๆ ) จุดทั้งหมดของคำตอบของฉันคือ OCP ไม่ได้พูดถึงการเปลี่ยนแปลงรหัส ทำลายหรืออื่น ๆ องค์ประกอบที่เป็นทั้งเปิดให้ขยายและปิดการปรับเปลี่ยนหรือไม่เช่นเดียวกับวิธีการที่อาจจะมีprivateหรือไม่ หากผู้เขียนทำprivateวิธีการในpublicภายหลังนั่นไม่ได้หมายความว่าพวกเขาละเมิดการควบคุมการเข้าถึง (1/2)
Derek Elkins

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

3
@JacquesB ขั้นแรกให้สังเกตอีกครั้งว่านี่เป็นการพูดถึงโมดูลที่สอดคล้องกับ OCP OCP เป็นคำแนะนำเกี่ยวกับวิธีการเขียนโมดูลเพื่อให้มีข้อ จำกัดว่าซอร์สโค้ดไม่สามารถเปลี่ยนแปลงได้อย่างไรก็ตามโมดูลสามารถขยายได้ ก่อนหน้านี้ในบทความที่เขาพูดเกี่ยวกับการออกแบบโมดูลที่ไม่เคยเปลี่ยนไม่ได้เกี่ยวกับการใช้กระบวนการจัดการการเปลี่ยนแปลงที่ไม่อนุญาตให้โมดูลเปลี่ยน อ้างถึงการแก้ไขคำตอบของคุณคุณไม่ได้ "ทำลาย OCP" โดยแก้ไขรหัสของโมดูล หาก "ขยาย" โมดูลจะต้องให้คุณแก้ไขซอร์สโค้ด (1/3)
Derek Elkins

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