ฉันจะป้องกันไม่ให้ส่วนหัวนรกได้อย่างไร


44

เรากำลังเริ่มโครงการใหม่ตั้งแต่เริ่มต้น ประมาณแปดนักพัฒนาระบบย่อยโหลหรือมากกว่านั้นแต่ละคนมีไฟล์ต้นฉบับสี่หรือห้าไฟล์

เราจะทำอย่างไรเพื่อป้องกัน“ หัวนรก”, AKA“ หัวสปาเก็ตตี้”?

  • หนึ่งส่วนหัวต่อไฟล์ต้นฉบับ?
  • บวกหนึ่งต่อระบบย่อย?
  • แยก typdefs, stucts & enums จากฟังก์ชั่นต้นแบบหรือไม่?
  • แยกระบบย่อยภายในออกจากสิ่งของภายนอกของระบบย่อยหรือไม่?
  • ยืนยันว่าทุกไฟล์เดียวไม่ว่าจะเป็นส่วนหัวหรือแหล่งที่มาจะต้องรวบรวมแบบสแตนด์อโลน

ฉันไม่ได้ขอวิธีที่ "ดีที่สุด" เพียงชี้ว่าควรระวังอะไรและอาจทำให้เกิดความเศร้าโศกเพื่อที่เราจะได้พยายามหลีกเลี่ยง

นี่จะเป็นโครงการ C ++ แต่ข้อมูล C จะช่วยผู้อ่านในอนาคต


16
รับสำเนาของการออกแบบซอฟต์แวร์ C ++ ขนาดใหญ่ไม่ใช่เพียงแค่สอนให้หลีกเลี่ยงปัญหาที่เกิดกับส่วนหัว แต่ยังมีปัญหาอื่น ๆ อีกมากมายเกี่ยวกับการอ้างอิงทางกายภาพระหว่างไฟล์ต้นฉบับและวัตถุในโครงการ C ++
Doc Brown

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

1
ฉันแน่ใจว่าฉันได้ทำงานกับคุณมาก่อน บ่อยครั้ง :-(
Mawg

5
สิ่งที่คุณอธิบายไม่ใช่โครงการขนาดใหญ่ การออกแบบที่ดีนั้นยินดีต้อนรับเสมอ แต่คุณอาจไม่เคยเจอปัญหา "ระบบขนาดใหญ่"
แซม

2
Boost จะมีวิธีการแบบรวมทุกอย่าง แต่ละคุณลักษณะ indviidual มีไฟล์ส่วนหัวของตัวเอง แต่แต่ละโมดูลขนาดใหญ่ยังมีส่วนหัวที่รวมทุกอย่าง สิ่งนี้กลายเป็นสิ่งที่ทรงพลังจริงๆในการย่อส่วนหัวนรกโดยไม่บังคับให้คุณรวม # สองสามร้อยไฟล์ในแต่ละครั้ง
Cort Ammon

คำตอบ:


39

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

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

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


ที่ที่จะได้รับหากินคือการเรียกใช้ฟังก์ชั่นระบบย่อยข้ามกับพารามิเตอร์ของประเภทที่ประกาศในระบบย่อยอื่น ๆ
Mawg

6
หากินหรือไม่ "#include <subsystem1.h>" ควรรวบรวม คุณประสบความสำเร็จได้อย่างไรขึ้นอยู่กับคุณ @ FrankPuffer: เพราะอะไร
gnasher729

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

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

4
@FrankPuffer: โปรดอย่าลบความคิดเห็นของคุณโดยเฉพาะอย่างยิ่งถ้าคนอื่น ๆ ตอบกลับพวกเขาเพราะมันทำให้การตอบสนองไร้บริบท คุณสามารถแก้ไขคำสั่งของคุณในความคิดเห็นใหม่ได้เสมอ ขอบคุณ! ฉันสนใจที่จะรู้ว่าสิ่งที่คุณพูดจริง ๆ แล้ว แต่ตอนนี้มันหายไป: (
MPW

18

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

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

ในการตอบคำถามเฉพาะของคุณ:

  • หนึ่งส่วนหัวต่อไฟล์ต้นฉบับ? → ใช่มันใช้งานได้ดีในกรณีส่วนใหญ่และทำให้ง่ายต่อการค้นหาสิ่งต่าง ๆ แต่อย่าทำให้เป็นศาสนา
  • บวกหนึ่งต่อระบบย่อย? → ไม่ทำไมคุณต้องทำเช่นนี้?
  • แยก typdefs, stucts & enums จากฟังก์ชั่นต้นแบบหรือไม่? → ไม่ฟังก์ชันและประเภทที่เกี่ยวข้องอยู่ด้วยกัน
  • แยกระบบย่อยภายในออกจากสิ่งของภายนอกของระบบย่อยหรือไม่? → ใช่แน่นอน สิ่งนี้จะลดการพึ่งพา
  • ยืนยันว่าไฟล์ทุกไฟล์ไม่ว่าจะเป็นส่วนหัวหรือแหล่งที่มาตามมาตรฐานสแตนอโลน → ใช่ไม่เคยต้องการให้มีส่วนหัวใด ๆ อยู่ก่อนส่วนหัวอื่น

12

นอกเหนือจากคำแนะนำอื่น ๆ ตามแนวของการลดการพึ่งพา (ส่วนใหญ่ใช้กับ C ++):

  1. รวมเฉพาะสิ่งที่คุณต้องการในที่ที่คุณต้องการ (ระดับต่ำสุดที่เป็นไปได้) เช่น. ไม่รวมอยู่ในส่วนหัวหากคุณต้องการการโทรในแหล่งที่มาเท่านั้น
  2. ใช้การประกาศไปข้างหน้าในส่วนหัวเมื่อเป็นไปได้ (ส่วนหัวมีเพียงพอยน์เตอร์หรือการอ้างอิงไปยังคลาสอื่น ๆ )
  3. ทำความสะอาดการรวมหลังจากการรีแฟคเตอร์แต่ละครั้ง (ใส่ความคิดเห็นดูที่การรวบรวมล้มเหลวย้ายไปที่นั่นลบบรรทัดรวมที่ยังคงคอมเม้นต์ไว้)
  4. อย่าแพ็คสิ่งอำนวยความสะดวกทั่วไปมากเกินไปลงในไฟล์เดียวกัน แยกพวกเขาโดยฟังก์ชั่น (เช่น Logger เป็นหนึ่งชั้นจึงหนึ่งส่วนหัวและหนึ่งแหล่งไฟล์; SystemHelper dito. ฯลฯ )
  5. ติดกับหลักการ OO แม้ว่าสิ่งที่คุณได้รับเป็นชั้นที่ประกอบด้วย แต่เพียงผู้เดียวของวิธีการคง (แทนที่จะเป็นฟังก์ชั่นแบบสแตนด์อโลน) - หรือใช้ namespace แทน
  6. สำหรับสิ่งอำนวยความสะดวกทั่วไปบางอย่างรูปแบบซิงเกิลค่อนข้างมีประโยชน์เนื่องจากคุณไม่จำเป็นต้องขออินสแตนซ์จากวัตถุอื่นที่ไม่เกี่ยวข้อง

5
ใน # 3 เครื่องมือรวมถึงสิ่งที่คุณใช้สามารถช่วยหลีกเลี่ยงวิธีการรวบรวมซ้ำด้วยตนเองและตรวจสอบด้วยตนเอง
RM

1
คุณช่วยอธิบายว่าประโยชน์ของซิงเกิลตันในบริบทนี้คืออะไร? ฉันไม่เข้าใจจริงๆ
Frank Puffer

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

1
# 2 (การประกาศล่วงหน้า) สามารถสร้างความแตกต่างอย่างมากในการรวบรวมเวลาและการตรวจสอบการพึ่งพา ตามที่คำตอบนี้ ( stackoverflow.com/a/9999752/509928 ) แสดงว่ามันใช้กับทั้ง C ++ และ C
Dave Compton

3
คุณสามารถใช้การประกาศไปข้างหน้าเมื่อส่งผ่านค่าเช่นกันตราบใดที่ฟังก์ชันไม่ได้ถูกกำหนดแบบอินไลน์ การประกาศฟังก์ชั่นไม่ได้เป็นบริบทที่จำเป็นต้องมีการกำหนดประเภทเต็มรูปแบบ (นิยามฟังก์ชั่นหรือการเรียกใช้ฟังก์ชั่นนั้นเป็นบริบท)
StoryTeller - Unslander Monica

6

หนึ่งเฮดเดอร์ต่อไฟล์ต้นฉบับซึ่งกำหนดว่าไฟล์ต้นฉบับใดที่ใช้ / ส่งออก

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

หลีกเลี่ยงการรวม (ลดการรวม) ไฟล์ส่วนหัวภายในไฟล์ส่วนหัวอื่น ๆ (เพื่อหลีกเลี่ยงการพึ่งพาแบบวนซ้ำ) สำหรับรายละเอียดดูคำตอบนี้เพื่อ "สองคลาสสามารถดูกันโดยใช้ C ++"

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


4

ฉันจะเรียกร้องคำถามของคุณโดยพื้นฐานแล้วไม่สามารถตอบได้เนื่องจากนรกมีสองประเภท:

  • แบบที่คุณต้องการรวมส่วนหัวที่แตกต่างกันนับล้านและใครในนรกสามารถจำได้ทั้งหมด และรักษารายชื่อส่วนหัวเหล่านั้นหรือไม่ ฮึ.
  • ชนิดที่คุณรวมสิ่งหนึ่งและพบว่าคุณได้รวม Tower of Babel ทั้งหมด (หรือฉันควรจะพูด Tower of Boost? ... )

สิ่งนี้คือถ้าคุณพยายามหลีกเลี่ยงอดีตที่คุณไปถึงระดับหนึ่งกับคนหลังและในทางกลับกัน

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


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

2
@ แนล: ฉันหมายถึงการหลีกเลี่ยงการขึ้นต่อกันแบบวนรอบของส่วนหัวไม่ใช่โค้ด ... หากคุณไม่หลีกเลี่ยงการขึ้นต่อกันของส่วนหัวแบบวงกลมคุณอาจไม่สามารถสร้างได้ แต่ใช่นำประเด็นไป +1
einpoklum - คืนสถานะโมนิก้า

1

decoupling

ในที่สุดมันก็เกี่ยวกับ decoupling ให้ฉันในตอนท้ายของวันที่ระดับการออกแบบขั้นพื้นฐานที่สุดโดยปราศจากความแตกต่างของลักษณะของคอมไพเลอร์และลิงเกอร์ของเรา ฉันหมายถึงคุณสามารถทำสิ่งต่าง ๆ เช่นทำให้แต่ละส่วนหัวกำหนดเพียงหนึ่งคลาสใช้ pimpls การประกาศไปข้างหน้าไปยังประเภทที่ต้องประกาศเท่านั้นไม่ได้กำหนดบางทีแม้แต่ใช้ส่วนหัวที่เพิ่งมีการประกาศไปข้างหน้า (เช่น:) <iosfwd>หนึ่งส่วนหัวต่อไฟล์ต้นฉบับ จัดระเบียบระบบอย่างต่อเนื่องตามประเภทของสิ่งที่ประกาศ / กำหนด ฯลฯ

เทคนิคในการลด "การพึ่งพาเวลารวบรวม"

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

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

ส่งต่อการประกาศไปยังประเภทภายนอก

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

หากคุณนึกภาพFoo.hppส่วนหัวที่มีลักษณะดังนี้:

#include "Bar.hpp"

และใช้เฉพาะBarในส่วนหัวด้วยวิธีที่ต้องการการประกาศไม่ใช่คำจำกัดความ แล้วมันอาจดูเหมือนว่าไม่มีเกมง่ายๆที่จะประกาศclass Bar;เพื่อหลีกเลี่ยงการทำให้คำจำกัดความที่Barมองเห็นได้ในส่วนหัว ยกเว้นในทางปฏิบัติบ่อยครั้งที่คุณจะพบหน่วยการคอมไพล์ส่วนใหญ่ที่ใช้Foo.hppยังคงต้องBarมีการกำหนดต่อไปด้วยภาระเพิ่มเติมของการรวมBar.hppตัวเองไว้ด้านบนFoo.hppหรือคุณพบกับสถานการณ์อื่นที่ช่วยได้จริงและ 99 % ของหน่วยการรวบรวมของคุณสามารถทำงานได้โดยไม่ต้องBar.hppยกเว้น แต่จะเพิ่มคำถามการออกแบบขั้นพื้นฐานเพิ่มเติม (หรืออย่างน้อยฉันคิดว่ามันควรจะทุกวันนี้) ว่าทำไมพวกเขาต้องเห็นการประกาศBarและทำไมFoo แม้จะต้องใส่ใจที่จะรู้เกี่ยวกับมันถ้ามันไม่เกี่ยวข้องกับกรณีการใช้งานมากที่สุด (ทำไมภาระการออกแบบที่มีการอ้างอิงกับอีกคนหนึ่งแทบจะไม่เคยใช้?)

เพราะแนวคิดเราไม่แยกFooจากBarกันจริงๆ เราเพิ่งสร้างมันขึ้นมาเพื่อให้ส่วนหัวของFooไม่ต้องการข้อมูลมากนักเกี่ยวกับส่วนหัวBarและนั่นก็ไม่ได้มีความสำคัญเท่ากับการออกแบบที่ทำให้ทั้งสองตัวนี้เป็นอิสระจากกันอย่างแท้จริง

สคริปต์ฝังตัว

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

ตัวอย่าง

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

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

ป้อนคำอธิบายรูปภาพที่นี่

ออกแบบออกแบบออกแบบ

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

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


1
คำตอบที่ยอดเยี่ยม! Althoguh ฉันต้องขุดนิดหน่อยเพื่อค้นหาว่าpimplsคือ :-)
Mawg

0

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

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

ฉันไม่คิดว่าจะแนะนำไฟล์ส่วนหัวจำนวนมาก โดยเฉพาะอย่างยิ่งฉันไม่แนะนำไฟล์ส่วนหัวหนึ่งไฟล์ต่อคลาสหรือไฟล์ส่วนหัวขนาดเล็กจำนวนมากที่มีสองสามบรรทัด

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

สิ่งที่คุณต้องการคือการระบุผู้พัฒนาหลักที่รับผิดชอบสำหรับแต่ละระบบย่อยและไฟล์

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

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

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

ขอให้สังเกตว่าใน C ++ มาตรฐานไฟล์ส่วนหัวจะดึงมากรหัส ตัวอย่างเช่น#include <vector>การดึงมากกว่าหมื่นบรรทัดใน GCC 6 ของฉันบน Linux (18100 บรรทัด) และ#include <map> ขยายไปเกือบ 40KLOC ดังนั้นหากคุณมีไฟล์ส่วนหัวขนาดเล็กจำนวนมากรวมถึงส่วนหัวมาตรฐานคุณจะต้องแยกวิเคราะห์หลายพันบรรทัดในระหว่างการบิลด์และเวลาการคอมไพล์ของคุณจะได้รับผลกระทบ นี่คือเหตุผลที่ฉันไม่ชอบมีซอร์ส C ++ ขนาดเล็กจำนวนมาก (ส่วนใหญ่ไม่กี่ร้อยบรรทัด) แต่ชอบที่มีไฟล์ C ++ น้อยกว่า แต่ใหญ่กว่า (หลายพันบรรทัด)

(ดังนั้นการมีไฟล์ C ++ ขนาดเล็กหลายร้อยไฟล์ซึ่งรวมถึง -even ทางอ้อม - ไฟล์ส่วนหัวมาตรฐานหลายไฟล์ให้เวลาในการสร้างมากซึ่งทำให้ผู้พัฒนารำคาญ)

ในรหัส C บ่อยครั้งที่ไฟล์ส่วนหัวจะขยายเป็นบางสิ่งที่เล็กกว่าดังนั้นการแลกเปลี่ยนจึงแตกต่างกัน

มองหาแรงบันดาลใจในการฝึกฝนก่อนหน้านี้ในโครงการซอฟต์แวร์ฟรีที่มีอยู่ (เช่นgitHub )

ขอให้สังเกตว่าการพึ่งพาสามารถจัดการกับระบบอัตโนมัติสร้างที่ดี ศึกษาเอกสารของ GNU แต่งหน้า ระวังการตั้งค่าตัวประมวลผลล่วงหน้าต่าง ๆ-Mให้กับ GCC (มีประโยชน์ในการสร้างการอ้างอิงโดยอัตโนมัติ)

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

(อย่ามุ่งเน้นความพยายามของคุณใน "ส่วนหัวนรก" - ซึ่งไม่ใช่ปัญหาสำหรับคุณ - แต่อย่าเน้นไปที่การออกแบบสถาปัตยกรรมที่ดี)


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

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