เป็นไปได้ไหมที่จะใช้ DRY โดยไม่ต้องมีเพศสัมพันธ์เพิ่มขึ้น?


14

สมมติว่าเรามีโมดูลซอฟต์แวร์ A ที่ใช้ฟังก์ชั่น F. โมดูล B อื่นใช้ฟังก์ชั่นเดียวกับ F '

มีหลายวิธีในการกำจัดรหัสที่ซ้ำกัน:

  1. ให้ใช้ F 'จาก B
  2. ให้ B ใช้ F จาก A
  3. ใส่ F ลงในโมดูล C ของตัวเองและปล่อยให้ทั้ง A และ B ใช้งาน

ตัวเลือกเหล่านี้ทั้งหมดสร้างการพึ่งพาเพิ่มเติมระหว่างโมดูล พวกเขาใช้หลักการ DRY ที่ค่าใช้จ่ายของการมีเพศสัมพันธ์ที่เพิ่มขึ้น

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

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

แก้ไข (เพื่อความกระจ่าง): ฉันคิดว่าความเท่าเทียมกันของ F และ F 'ไม่ใช่แค่เรื่องบังเอิญ หาก F ต้องได้รับการแก้ไข F 'จะต้องได้รับการแก้ไขในลักษณะเดียวกัน


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

3
หลักการของ OO นี้มีความขัดแย้งกัน ในกรณีส่วนใหญ่คุณต้องค้นหาการแลกเปลี่ยนที่เหมาะสม แต่ IMHO หลักการของ DRY นั้นมีค่าที่สุด ดังที่ @jrh เขียนว่า: การมีพฤติกรรมแบบเดียวกันในหลายสถานที่เป็นฝันร้ายที่ควรหลีกเลี่ยงค่าใช้จ่ายใด ๆ การค้นหาว่าคุณลืมอัปเดตหนึ่งในสำเนาที่ซ้ำซ้อนในการผลิตอาจทำให้ธุรกิจของคุณแย่ลงได้
ทิโมธี Truckle

2
@ TimothyTruckle: เราไม่ควรเรียกพวกเขาว่าหลักการ OO เพราะพวกเขาใช้กับกระบวนทัศน์การเขียนโปรแกรมอื่น ๆ เช่นกัน และใช่ DRY นั้นมีค่า แต่ก็อันตรายเช่นกัน ไม่เพียงเพราะมันมีแนวโน้มที่จะสร้างการพึ่งพาและความซับซ้อน มันมักใช้กับข้อมูลซ้ำที่เกิดจากความบังเอิญซึ่งมีเหตุผลที่แตกต่างกันในการเปลี่ยนแปลง
Frank Puffer

1
... บางครั้งเมื่อฉันพบเจอสถานการณ์นี้ฉันสามารถแยก F เป็นส่วน ๆ ที่สามารถใช้ประโยชน์ได้ในสิ่งที่ A ต้องการและสิ่งที่ B ต้องการ
jrh

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

คำตอบ:


14

ตัวเลือกเหล่านี้ทั้งหมดสร้างการพึ่งพาเพิ่มเติมระหว่างโมดูล พวกเขาใช้หลักการ DRY ที่ค่าใช้จ่ายของการมีเพศสัมพันธ์ที่เพิ่มขึ้น

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

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

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


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

1
ไม่เป็นไรตัวร้ายของฉัน: meta.stackexchange.com/a/263672/143358
Basilevs

@ FrankPuffer ดีกว่าไหม
candied_orange

3

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

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

  • กำหนดอินเตอร์เฟส F Aใน A ซึ่งมีฟังก์ชันการทำงานของ F
  • นิยามอินเตอร์เฟส F Bใน B
  • ใส่ F ใน C
  • สร้างโมดูล D เพื่อจัดการการพึ่งพาทั้งหมด (ขึ้นอยู่กับ A, B และ C)
  • ปรับ F เป็น F Aและ F B เป็น D
  • wrapper Inject (pass) ถึง A และ B

ด้วยวิธีนี้การพึ่งพาประเภทเดียวที่คุณจะมีคือ D ขึ้นอยู่กับแต่ละโมดูลอื่น ๆ

หรือลงทะเบียน C ในคอนเทนเนอร์ของแอปพลิเคชันด้วยการฉีดพึ่งพาในตัวและเพลิดเพลินกับโชคชะตาของการลงทะเบียนลูปและการหยุดชะงักของคลาสการรันไทม์แบบอัตโนมัติ


1
+1 แต่ด้วยข้อแม้ที่ชัดเจนว่าโดยปกติแล้วจะเป็นการแลกเปลี่ยนที่ไม่ดี ความปลอดภัยแบบคงที่นั้นมีไว้สำหรับการป้องกันของคุณและหลีกเลี่ยงมันด้วยการกระทำที่น่ากลัวในระยะไกลเพียงแค่ขอข้อบกพร่องที่ยากต่อการติดตามลงไปอีกเมื่อความซับซ้อนของโครงการของคุณเพิ่มขึ้นเล็กน้อย ...
Mason Wheeler

1
DI บอกได้ไหมว่าจะทำลายการพึ่งพาได้จริงหรือ? คุณต้องมีส่วนต่อประสานกับลายเซ็นของ F เพื่อนำไปใช้งาน แต่ก็ยังถ้าโมดูล A ใช้ r จาก F มันเป็นไปได้โดยไม่คำนึงถึง C ที่ถูกฉีดที่รันไทม์หรือเชื่อมโยงโดยตรงฉันจะไม่ทำลายการพึ่งพาข้อผิดพลาดเพียงเลื่อนการล้มเหลวหากไม่มีการพึ่งพา
max630

@ max630 มันมาแทนที่การพึ่งพาการใช้งานกับสัญญาหนึ่งซึ่งเป็นที่อ่อนแอ
Basilevs

@ max630 คุณถูกต้อง DI ไม่สามารถพูดได้ว่าเป็นการทำลายการพึ่งพา DI คือความจริงแล้วเป็นวิธีการแนะนำการพึ่งพาและเป็นมุมฉากของคำถามที่ถามเกี่ยวกับการแต่งงานกัน การเปลี่ยนแปลงใน F (หรือส่วนต่อประสานที่ห่อหุ้ม) จะยังคงต้องการการเปลี่ยนแปลงทั้ง A และ B
สไลด์ด้านข้างของกษัตริย์

1

ฉันไม่แน่ใจว่าคำตอบที่ไม่มีบริบทเพิ่มเติมเหมาะสมหรือไม่

ไม่Aอยู่แล้วขึ้นอยู่กับBหรือในทางกลับกัน? - ในกรณีนี้เราอาจมีทางเลือกที่ชัดเจนสำหรับบ้านFในกรณีที่เราอาจจะมีทางเลือกที่ชัดเจนของบ้าน

ทำAและBแบ่งปันการอ้างอิงทั่วไปที่อาจเป็นบ้านที่ดีอยู่แล้วFหรือไม่?

ใหญ่ / ซับซ้อนFแค่ไหน? มีอะไรอีกบ้างFขึ้นอยู่กับ?

เป็นโมดูลAและBใช้ในโครงการเดียวกันหรือไม่

จะAและBลงเอยด้วยการแบ่งปันการพึ่งพาทั่วไปบางอย่างหรือไม่

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

ในทางตรงกันข้ามถ้าคุณกำลังพูดถึง Java หรือ C # DLLs ที่รวมค่อนข้างราบรื่นในสภาพแวดล้อมการดำเนินการเดียวนั่นเป็นอีกเรื่องหนึ่ง


ฟังก์ชั่นเป็นนามธรรมและรองรับ DRY

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

ดังนั้นฉันจะยืนยันที่จะสร้างสิ่งที่เป็นนามธรรมที่ดีกว่าAและBขึ้นอยู่กับเพียงแค่ย้ายฟังก์ชั่นเดียวไปยังโมดูลใหม่Cจะขึ้นอยู่กับกว่าเพียงการย้ายฟังก์ชันหนึ่งเดียวในโมดูลใหม่ 

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


2
มันเป็นอันตรายหรือไม่ที่จะจับคู่บทคัดย่อและกราฟพึ่งพา?
Basilevs

Does A already depend on B or vice versa? — in which case we might have an obvious choice of home for F.นี่ถือว่าสมมติว่า A จะพึ่งพา B (หรือกลับกัน) เสมอซึ่งเป็นข้อสันนิษฐานที่อันตรายมาก ความจริงที่ว่า OP เห็น F ว่าไม่ใช่ส่วนหนึ่งของ A (หรือ B) โดยปริยายแสดงให้เห็นว่า F มีอยู่จริงโดยไม่ต้องมีอยู่ในห้องสมุดทั้งสอง ถ้า F เป็นของหนึ่งไลบรารี (เช่นวิธีการขยาย DbContext (F) และไลบรารี wrapper Entity Framework (A หรือ B)) คำถามของ OP จะเป็นสิ่งที่สงสัย
Flater

0

คำตอบที่นี่ที่มุ่งเน้นไปที่ทุกวิธีที่คุณสามารถ "ลด" ปัญหานี้กำลังก่อความเสียหายให้กับคุณ และ "การแก้ปัญหา" เพียงนำเสนอวิธีการต่าง ๆ ในการสร้างการแต่งงานไม่ใช่วิธีการแก้ปัญหาที่แท้จริง

ความจริงก็คือว่าไม่เข้าใจปัญหาที่คุณสร้างขึ้น ปัญหาในตัวอย่างของคุณไม่มีอะไรเกี่ยวข้องกับ DRY แต่ค่อนข้างกว้าง (มากขึ้น) กับการออกแบบแอปพลิเคชัน

ถามตัวเองว่าทำไมโมดูล A และ B แยกกันถ้าทั้งคู่ขึ้นอยู่กับฟังก์ชัน F เดียวกัน แน่นอนว่าคุณจะมีปัญหากับการจัดการการพึ่งพา / นามธรรม / การมีเพศสัมพันธ์ / คุณชื่อมันถ้าคุณมุ่งมั่นที่จะออกแบบที่ไม่ดี

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

DRY เป็นตัวการที่สามารถใช้เพื่อเปิดเผยการออกแบบที่ไม่ดี หากคุณไม่สามารถทำ DRY ได้ ( เมื่อใดนำไปใช้จริง - สังเกตการแก้ไขของคุณ) เนื่องจากโครงสร้างของแอปพลิเคชันของคุณมันเป็นสัญญาณที่ชัดเจนว่าโครงสร้างกลายเป็นความรับผิดชอบ นี่คือเหตุผลที่“ การปรับโครงสร้างอย่างต่อเนื่อง” ก็เป็นหลักการที่ต้องติดตามด้วยเช่นกัน

ABC ของหลักการออกแบบอื่น ๆ (SOLID, DRY และอื่น ๆ ) มีทั้งหมดที่ใช้ในการเปลี่ยนแปลง (รวมถึงการปรับโครงสร้างใหม่) แอปพลิเคชันที่ไม่เจ็บปวด มุ่งเน้นไปที่และปัญหาอื่น ๆ ทั้งหมดเริ่มหายไป


คุณแนะนำให้มีหนึ่งโมดูลในทุก ๆ แอปพลิเคชันหรือไม่?
Basilevs

@Basilevs ไม่ใช่อย่างแน่นอน (เว้นแต่จะได้รับการรับประกัน) ฉันขอแนะนำให้มีโมดูลแยกได้อย่างสมบูรณ์มากที่สุด ท้ายที่สุดนั่นคือจุดประสงค์ทั้งหมดของโมดูล
สไลด์ข้างกษัตริย์

คำถามหมายถึงโมดูล A และ B ที่จะมีฟังก์ชั่นที่ไม่เกี่ยวข้องและจะถูกแยกออกไปแล้วทำไมและวิธีการที่พวกเขาควรจะหยุดอยู่? อะไรคือวิธีแก้ปัญหาของคุณตามที่ระบุไว้?
Basilevs

@Basilevs คำถามที่แสดงถึง A และ B ยังไม่ได้ถูกสร้างแบบจำลองอย่างถูกต้อง นี่เป็นข้อบกพร่องโดยธรรมชาติที่ก่อให้เกิดปัญหาตั้งแต่แรก แน่นอนความจริงง่ายๆที่พวกเขาทำมีอยู่ไม่ได้เป็นหลักฐานที่แสดงว่าพวกเขาควรจะอยู่ นั่นคือสิ่งที่ฉันทำข้างต้น เห็นได้ชัดว่าการออกแบบทางเลือกเป็นสิ่งจำเป็นเพื่อหลีกเลี่ยงการทำลาย DRY สิ่งสำคัญคือต้องเข้าใจว่าจุดประสงค์ที่สำคัญของ“ หลักการออกแบบ” ทั้งหมดนี้คือการทำให้แอปพลิเคชันเปลี่ยนแปลงได้ง่ายขึ้น
สไลด์ข้างกษัตริย์

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

0

ตัวเลือกเหล่านี้ทั้งหมดสร้างการพึ่งพาเพิ่มเติมระหว่างโมดูล พวกเขาใช้หลักการ DRY ที่ค่าใช้จ่ายของการมีเพศสัมพันธ์ที่เพิ่มขึ้น

ฉันมีความคิดเห็นที่แตกต่างกันอย่างน้อยสำหรับตัวเลือกที่สาม:

จากคำอธิบายของคุณ:

  • ความต้องการเอฟ
  • B ต้องการ F
  • ทั้ง A และ B ไม่ต้องการซึ่งกันและกัน

การใส่ F ในโมดูล C จะไม่เพิ่มการเชื่อมต่อเนื่องจากทั้ง A และ B ต้องการคุณสมบัติของ C อยู่แล้ว

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