จะทำอย่างไรกับไฟล์ต้นฉบับ C ++ 11,000 บรรทัด


229

ดังนั้นเราจึงมีไฟล์ต้นฉบับขนาดใหญ่ (มีขนาด 11,000 บรรทัดหรือไม่) ไฟล์ mainmodule.cpp ในโครงการของเราและทุกครั้งที่ฉันต้องแตะมันฉันประจบประแจง

เนื่องจากไฟล์นี้มีขนาดกลางและใหญ่มากจึงมีการสะสมรหัสมากขึ้นเรื่อย ๆ และฉันไม่สามารถนึกถึงวิธีที่ดีที่จะทำให้มันเริ่มหดตัวลง

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

ไฟล์นั้นมี "คลาสหลัก" (การมอบหมายงานภายในและการประสานงานหลัก) ของโปรแกรมของเราดังนั้นทุกครั้งที่มีการเพิ่มฟีเจอร์มันจะมีผลกับไฟล์นี้และทุกครั้งที่มันเติบโต :-(

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

(หมายเหตุเกี่ยวกับเครื่องมือ: เราใช้ C ++ ด้วยVisual Studio; เราใช้AccuRevเป็นSCCแต่ฉันคิดว่าประเภทของSCCไม่สำคัญที่นี่; เราใช้Araxis Mergeเพื่อทำการเปรียบเทียบจริงและการรวมไฟล์)


15
@BoltClock: จริงแล้ว Vim จะเปิดขึ้นมาค่อนข้างเร็ว
ereOn

58
เส้น 69305 และการนับ ไฟล์ในแอปพลิเคชันของเราที่เพื่อนร่วมงานของฉันทิ้งส่วนใหญ่ของรหัสมาไม่สามารถต้านทานการโพสต์ที่นี่ ฉันไม่มีใครใน บริษัท ของฉันที่จะรายงานเรื่องนี้กับ
Agnel Kurian

204
ฉันไม่เข้าใจ ความคิดเห็น "ออกจากงานนั้น" จะได้รับ upvotes มากมายได้อย่างไร บางคนดูเหมือนจะอาศัยอยู่ในแดนสวรรค์ที่ซึ่งโครงการทั้งหมดถูกเขียนขึ้นมาจากศูนย์และ / หรือใช้เปรียว 100%, TDD, ... (ใส่คำศัพท์ใด ๆ ของคุณที่นี่)
Stefan

39
@tefan: เมื่อต้องเผชิญกับรหัสฐานที่คล้ายกันฉันทำอย่างนั้น ฉันไม่ได้คิดว่าการใช้จ่าย 95% ของเวลาของฉันทำงานกับความยุ่งเหยิงในฐานรหัสอายุ 10 ปีและ 5% เขียนรหัสจริง จริงๆแล้วมันเป็นไปไม่ได้ที่จะทดสอบบางแง่มุมของระบบ (และฉันไม่ได้หมายถึงการทดสอบหน่วยฉันหมายถึงใช้รหัสจริงเพื่อดูว่ามันทำงานได้หรือไม่) ฉันไม่ได้อยู่ในช่วงทดลองใช้งานเป็นเวลา 6 เดือนฉันรู้สึกเบื่อกับการต่อสู้ที่หายไปและการเขียนรหัสที่ฉันไม่สามารถทำได้
Binary Worrier

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

คำตอบ:


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

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

ดังนั้นเราได้กำจัดรหัสที่เหมือนกันทุกที่และรหัสที่เฉพาะเจาะจงกับสาขาบางสาขา

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

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


สำหรับการรวม: ฉันดู GIT และฉันดู SVN และฉันดูที่ Perforce และให้ฉันบอกคุณว่าไม่มีอะไรที่ฉันเคยเห็นที่ใดก็ได้เต้น AccuRev + Araxis สำหรับสิ่งที่เราทำ :-) (แม้ว่า GIT สามารถทำสิ่งนี้ได้ [ stackoverflow.com/questions/1728922/ ...... ] และ AccuRev ไม่สามารถทำได้ - ทุกคนต้องตัดสินใจด้วยตัวเองหากนี่เป็นส่วนหนึ่งของการรวมหรือวิเคราะห์ประวัติศาสตร์)
Martin Ba

ยุติธรรมเพียงพอ - บางทีคุณอาจมีเครื่องมือที่ดีที่สุด ความสามารถของ Git ในการรวมการเปลี่ยนแปลงที่เกิดขึ้นในไฟล์ A บนสาขา X เข้ากับไฟล์ B ในสาขา Y ควรทำให้แยกไฟล์ที่แยกย่อยได้ง่ายขึ้น แต่สันนิษฐานว่าระบบที่คุณใช้มีข้อดีที่คุณต้องการ อย่างไรก็ตามฉันไม่ได้เสนอให้คุณเปลี่ยนไปใช้คอมไพล์เพียงแค่บอกว่า SCC สร้างความแตกต่างได้ที่นี่ แต่ถึงกระนั้นฉันก็เห็นด้วยกับคุณว่านี่สามารถลดราคาได้ :-)
Steve Jessop

129

การผสานจะไม่เป็นฝันร้ายที่ยิ่งใหญ่อย่างที่จะเป็นเมื่อคุณจะได้รับไฟล์ 30,000 LOC ในอนาคต ดังนั้น:

  1. หยุดเพิ่มรหัสเพิ่มเติมลงในไฟล์นั้น
  2. แยกมัน.

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


@ มาร์ติน: โชคดีที่คุณยังไม่ได้วางไฟล์ของคุณที่นี่ดังนั้นฉันจึงไม่มีความคิดเกี่ยวกับโครงสร้างของมัน แต่ความคิดทั่วไปคือการแยกมันออกเป็นส่วน ๆ ส่วนตรรกะดังกล่าวอาจมีกลุ่มของฟังก์ชั่นจาก "ชั้นเรียนหลัก" ของคุณหรือคุณสามารถแยกออกเป็นชั้นเสริมต่างๆ
Kirill V. Lyadvinsky

3
ด้วยเวอร์ชันการบำรุงรักษา 10 รายการและผู้พัฒนาที่ใช้งานอยู่จำนวนมากจึงไม่น่าเป็นไปได้ที่ไฟล์จะถูกแช่แข็งเป็นเวลานานพอ
Kobi

9
@Martin คุณมีรูปแบบ GOF สองสามอย่างที่จะทำเคล็ดลับFacadeเดียวที่แมปฟังก์ชั่นของ mainmodule.cpp อีกวิธีหนึ่ง (ฉันแนะนำด้านล่าง) สร้างชุดคำสั่งที่แต่ละแผนที่ไปยังฟังก์ชัน / คุณสมบัติของ mainmodule.app (ฉันได้ขยายคำตอบนี้ไปแล้ว)
ocodo

2
ใช่เห็นด้วยอย่างสิ้นเชิงในบางจุดคุณต้องหยุดเพิ่มรหัสหรือในที่สุดจะเป็น 30k, 40k, 50k, kaboom mainmodule เพียง seg faulted :-)
Chris

67

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

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

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

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

ไม่ว่าในกรณีใดดูเหมือนว่าเป็นโครงการสำคัญโชคดีมาก :)


18
มันมีกลิ่นมาก: มันมีกลิ่นเหมือนหยดป้องกันรูปแบบอยู่ในบ้านดา ... en.wikipedia.org/wiki/God_object อาหารจานโปรดของเขาคือสปาเก็ตตี้รหัส: en.wikipedia.org/wiki/Spaghetti_code :-)
jdehaan

@jdehaan: ผมพยายามที่จะเป็นทูตเกี่ยวกับมัน :)
ไบรอันรัสมุส

+1 จากฉันด้วยฉันไม่กล้าสัมผัสแม้แต่รหัสที่ซับซ้อนที่ฉันเขียนโดยไม่มีการทดสอบเพื่อครอบคลุม
Danny Thomas

49

ทางออกเดียวที่ฉันเคยจินตนาการถึงปัญหาดังต่อไปนี้ กำไรที่เกิดขึ้นจริงโดยวิธีการที่อธิบายไว้คือความก้าวหน้าของการวิวัฒนาการ ไม่มีการปฏิวัติที่นี่มิฉะนั้นคุณจะมีปัญหาอย่างรวดเร็ว

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

เมื่อดำเนินการเสร็จแล้วคุณจะสามารถเพิ่มฟังก์ชันการทำงานใหม่ในคลาสใหม่ได้

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

ฉันรู้ว่ามันไม่สมบูรณ์แบบ แต่ฉันหวังว่ามันจะช่วยได้และกระบวนการจะต้องปรับให้เข้ากับความต้องการของคุณ!

ข้อมูลเพิ่มเติม

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

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


การอ้างอิงเกี่ยวกับวิธีที่ GIT ทำ / วิธีที่คุณใช้กับ GIT นั้นจะได้รับการต้อนรับมากที่สุด
Martin Ba

@Martin Git ทำโดยอัตโนมัติ
Matthew

4
@Martin: Git ทำโดยอัตโนมัติ - เนื่องจากไม่ได้ติดตามไฟล์จึงติดตามเนื้อหา จริง ๆ แล้วมันยากมากในการคอมไพล์เพียง "รับประวัติของไฟล์เดียว"
Arafangion

1
@Martin youtube.com/watch?v=4XpnKHJAok8เป็นการพูดคุยที่ Torvalds พูดถึงคอมไพล์ เขากล่าวถึงในภายหลังในการพูดคุย
Matthew

6
@ มาร์ตินลองดูคำถามนี้: stackoverflow.com/questions/1728922/…
Benjol


25

ให้ฉันเดา: ลูกค้าสิบรายที่มีชุดคุณสมบัติที่แตกต่างกันและผู้จัดการฝ่ายขายที่ส่งเสริม "การปรับแต่ง"? ฉันเคยทำงานเกี่ยวกับผลิตภัณฑ์เช่นนั้นมาก่อน เรามีปัญหาเดียวกันเป็นหลัก

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

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

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

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

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


2
ฉันยอมรับว่าเป้าหมายควรมีซอฟต์แวร์หนึ่งเวอร์ชัน แต่จะดีกว่าถ้าใช้ไฟล์ปรับแต่ง (รันไทม์) และไม่คอมไพล์การควบคุมเวลา
Esben Skov Pedersen

หรือแม้แต่ "คลาสการกำหนดค่า" สำหรับบิลด์ของลูกค้าแต่ละราย
tc

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

22

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

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

ฉันคิดว่าคุณมีตัวเลือกเหล่านี้เท่านั้น:

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

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

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


19

ปัญหานี้ได้รับการจัดการอย่างแน่นอนในหนึ่งบทของหนังสือ "การทำงานอย่างมีประสิทธิภาพด้วยรหัสมรดก" ( http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052 )


informit.com/store/product.aspx?isbn=0131177052ทำให้สามารถดู TOC ของหนังสือเล่มนี้ (และ 2 บทตัวอย่าง) บทที่ 20 นานแค่ไหน (เพียงเพื่อรับความรู้สึกของวิธีที่มีประโยชน์ก็อาจจะมี.)
มาร์ตินบา

17
บทที่ 20 มีความยาว 10,000 บรรทัด แต่ผู้เขียนหาวิธีแยกมันออกเป็นชิ้นย่อย ... 8)
Tony Delroy

1
มันประมาณ 23 หน้า แต่มี 14 ภาพ ฉันคิดว่าคุณควรจะได้มันคุณจะรู้สึกมั่นใจมากขึ้นในการตัดสินใจว่าจะทำยังไง
Emile Vrijdags

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

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

14

ฉันคิดว่าคุณควรสร้างชุดคำสั่งคลาสที่แมปไปยังจุด API ของ mainmodule.cpp

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

แน่นอนว่าด้วยคลาสเดี่ยวของ KLOC 11 รหัสนั้นอาจมีการเชื่อมโยงและเปราะบาง แต่การสร้างคลาสคำสั่งแต่ละรายการจะช่วยได้มากกว่ากลยุทธ์พร็อกซี / ซุ้มอื่น ๆ

ฉันไม่ได้อิจฉางาน แต่เมื่อเวลาผ่านไปปัญหานี้จะเลวร้ายยิ่งขึ้นถ้ามันไม่ได้จัดการ

ปรับปรุง

ฉันขอแนะนำว่ารูปแบบคำสั่งนั้นดีกว่าสำหรับ Facade

การบำรุงรักษา / การจัดชั้นเรียน Command ที่แตกต่างกันจำนวนมากใน Facade monolithic Facade นั้นค่อนข้างดีกว่า การทำแผนที่หนึ่งหน้าให้เป็นไฟล์ KLOC 11 ไฟล์อาจจะต้องแยกย่อยออกเป็นสองกลุ่ม

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

แน่นอนว่าตัวเลือกทั้งสองนั้นดีกว่าไฟล์ KLOC 11 ตัวเดียวและไฟล์ที่กำลังเติบโต


+1 ทางเลือกสำหรับโซลูชันที่ฉันเสนอด้วยแนวคิดเดียวกัน: เปลี่ยน API เพื่อแยกปัญหาใหญ่ออกเป็นเล็ก ๆ
Benoît

13

คำแนะนำที่สำคัญอย่างหนึ่ง: อย่าผสม refactoring และ bugfixes สิ่งที่คุณต้องการคือเวอร์ชันของโปรแกรมที่เหมือนกับรุ่นก่อนหน้ายกเว้นว่าซอร์สโค้ดแตกต่างกัน

วิธีหนึ่งอาจเป็นการเริ่มแยกฟังก์ชันที่ใหญ่ที่สุดน้อยที่สุด / เป็นไฟล์ของตัวเองแล้วรวมกับส่วนหัว (ดังนั้นเปลี่ยน main.cpp เป็นรายการของ #includes ซึ่งฟังกลิ่นรหัสในตัวเอง * ฉันไม่ แม้ว่า C ++ Guru) แต่อย่างน้อยตอนนี้ก็แยกเป็นไฟล์)

จากนั้นคุณสามารถลองเปลี่ยนการบำรุงรักษาทั้งหมดไปยัง main.cpp "ใหม่" หรือโครงสร้างของคุณ อีกครั้ง: ไม่มีการเปลี่ยนแปลงหรือแก้ไขข้อผิดพลาดอื่น ๆ เนื่องจากการติดตามสิ่งเหล่านั้นทำให้สับสนเหมือนเป็นนรก

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

เห็นได้ชัดว่าต้องมีวินัยในทีมจริง ๆ แล้วใช้ไฟล์แยกและไม่เพียง แต่เพิ่มสิ่งใหม่ ๆ ลงใน main.cpp ตลอดเวลา แต่อีกครั้งการพยายามทำ refactor ขนาดใหญ่อาจไม่ใช่วิธีที่ดีที่สุด


1
+1 สำหรับแฟออกและกลับมาใน #include. ถ้าคุณทำอย่างนี้ทั้งหมด 10 สาขา (บิตของการทำงานมี แต่จัดการได้) ที่คุณยังคงมีปัญหาอื่น ๆ ที่การโพสต์การเปลี่ยนแปลงทั่วสาขาทั้งหมดของคุณ แต่ที่ปัญหา wouldn' t ได้ขยาย (จำเป็น) มันน่าเกลียดไหม ใช่มันยังคงเป็นอยู่ แต่อาจทำให้เกิดปัญหาเล็กน้อย หลังจากใช้เวลาหลายปีในการบำรุงรักษาและให้บริการสำหรับผลิตภัณฑ์ที่ยอดเยี่ยมจริงๆฉันรู้ว่าการบำรุงรักษานั้นเกี่ยวข้องกับความเจ็บปวดมากมาย อย่างน้อยที่สุดเรียนรู้จากมันและทำหน้าที่เป็นเรื่องเตือนให้ผู้อื่น
Jay

10

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

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

ฉันต้องบอกว่ามันใช้เวลาประมาณครึ่งปีและไม่มีการพัฒนาฐานรหัสเก่าข้างข้อบกพร่องในช่วงเวลานั้น


แก้ไข:

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

ซอฟต์แวร์นี้ใช้สำหรับระบบฝังตัวซึ่งเป็นเครื่องพิมพ์ฉลาก

อีกจุดที่ฉันควรเพิ่มคือในทางทฤษฎีโครงการคือ C ++ แต่มันไม่ใช่ OO เลยมันน่าจะเป็น C เวอร์ชั่นใหม่เป็นแบบเชิงวัตถุ


9
ทุกครั้งที่ฉันได้ยิน "ตั้งแต่เริ่มต้น" ในหัวข้อเกี่ยวกับการปรับโครงสร้างใหม่ฉันฆ่าลูกแมว!
Kugel

ฉันเคยอยู่ในสถานการณ์ที่คล้ายกันมากถึงแม้ว่าโปรแกรมหลักที่ฉันต้องได้รับคือ 9000 LOC และนั่นก็ไม่ดีพอ
AndyUK

8

ตกลงดังนั้นส่วนใหญ่เขียนใหม่ API ของรหัสการผลิตเป็นความคิดที่ไม่ดีเป็นจุดเริ่มต้น สองสิ่งต้องเกิดขึ้น

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

สองคุณต้องใช้เวอร์ชันการผลิตนี้และสร้างสาขาที่จัดการการสร้างโดยใช้คำสั่ง preprocessing เพื่อแยกไฟล์ขนาดใหญ่ การแยกการรวบรวมโดยใช้คำสั่ง preprocessor ของ JUST (#ifdefs, #includes, #endifs) นั้นง่ายกว่าการเข้ารหัส API มันง่ายกว่าสำหรับ SLA ของคุณและการสนับสนุนอย่างต่อเนื่อง

ที่นี่คุณสามารถตัดการทำงานที่เกี่ยวข้องกับระบบย่อยเฉพาะภายในคลาสและวางไว้ในไฟล์ที่บอกว่า mainloop_foostuff.cpp และรวมไว้ใน mainloop.cpp ในตำแหน่งที่ถูกต้อง

หรือ

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

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

โครงสร้างพื้นฐานคือ mainclass.cpp ของคุณจะรวมไฟล์ใหม่ชื่อ MainClassComponents.cpp หลังจากบล็อกข้อความสั่งดังนี้:

#if VARIANT == 1
#  define Uses_Component_1
#  define Uses_Component_2
#elif VARIANT == 2
#  define Uses_Component_1
#  define Uses_Component_3
#  define Uses_Component_6
...

#endif

#include "MainClassComponents.cpp"

โครงสร้างหลักของไฟล์ MainClassComponents.cpp จะอยู่ที่นั่นเพื่อหาการพึ่งพาภายในองค์ประกอบย่อยดังนี้:

#ifndef _MainClassComponents_cpp
#define _MainClassComponents_cpp

/* dependencies declarations */

#if defined(Activate_Component_1) 
#define _REQUIRES_COMPONENT_1
#define _REQUIRES_COMPONENT_3 /* you also need component 3 for component 1 */
#endif

#if defined(Activate_Component_2)
#define _REQUIRES_COMPONENT_2
#define _REQUIRES_COMPONENT_15 /* you also need component 15 for this component  */
#endif

/* later on in the header */

#ifdef _REQUIRES_COMPONENT_1
#include "component_1.cpp"
#endif

#ifdef _REQUIRES_COMPONENT_2
#include "component_2.cpp"
#endif

#ifdef _REQUIRES_COMPONENT_3
#include "component_3.cpp"
#endif


#endif /* _MainClassComponents_h  */

และตอนนี้สำหรับแต่ละองค์ประกอบที่คุณสร้างไฟล์ component_xx.cpp

แน่นอนฉันกำลังใช้ตัวเลข แต่คุณควรใช้ตรรกะมากกว่าตามรหัสของคุณ

การใช้ตัวประมวลผลล่วงหน้าช่วยให้คุณสามารถแยกสิ่งต่าง ๆ ได้โดยไม่ต้องกังวลกับการเปลี่ยนแปลง API ซึ่งเป็นฝันร้ายในการผลิต

เมื่อคุณตัดสินการผลิตแล้วคุณสามารถทำงานออกแบบได้จริง


ดูเหมือนว่าผลลัพธ์ที่ได้มาจากการทำงาน แต่เจ็บปวดในขั้นต้น
JBRWilkinson

ที่จริงแล้วมันเป็นเทคนิคที่ใช้ในคอมไพเลอร์ Borland C ++ เพื่อเลียนแบบสไตล์ Pascal ใช้สำหรับจัดการไฟล์ส่วนหัว โดยเฉพาะอย่างยิ่งเมื่อพวกเขาทำพอร์ตเริ่มต้นของระบบ Window-based Windowing
Elf King

8

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

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

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


4

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


และเช่นเคยฉันต้องการคำอธิบายสำหรับ downvote
Björn Pollex

4

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


4

นี่ไม่ใช่คำตอบสำหรับปัญหาใหญ่ แต่เป็นการแก้ปัญหาเชิงทฤษฎีสำหรับชิ้นส่วนเฉพาะ:

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

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

  • เรียกใช้สคริปต์ ลบไฟล์ต้นฉบับ

  • เมื่อคุณต้องการผสานจากสาขาก่อนอื่นให้สร้างไฟล์ขนาดใหญ่โดยการต่อชิ้นส่วนกลับเข้าด้วยกันทำการผสานแล้วทำการแบ่งใหม่

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


4

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

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


ฉันคิดว่าคนอื่นเสนอสิ่งที่คล้ายกัน สั้นและตรงประเด็นและฉันคิดว่านี่อาจเป็นจุดเริ่มต้นที่ถูกต้องสำหรับปัญหาดั้งเดิม
Martin Ba

3

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

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


3

ความเห็นอกเห็นใจของฉัน - ในงานก่อนหน้าของฉันฉันพบสถานการณ์ที่คล้ายกันกับไฟล์ที่มีขนาดใหญ่กว่าที่คุณต้องจัดการหลายเท่า วิธีแก้ไขคือ:

  1. เขียนโค้ดเพื่อทดสอบฟังก์ชันในโปรแกรมที่เป็นปัญหา ดูเหมือนว่าคุณจะไม่มีสิ่งนี้อยู่ในมือ ...
  2. ระบุรหัสบางอย่างที่สามารถแยกออกเป็นคลาสผู้ช่วย / อรรถประโยชน์ ไม่จำเป็นต้องมีขนาดใหญ่เพียงบางสิ่งที่ไม่ได้เป็นส่วนหนึ่งของชั้นเรียน 'หลัก' ของคุณอย่างแท้จริง
  3. ทำการ Refactor รหัสที่ระบุไว้ใน 2. ลงในคลาสที่แยกต่างหาก
  4. รันการทดสอบของคุณอีกครั้งเพื่อให้แน่ใจว่าไม่มีอะไรผิดพลาด
  5. เมื่อคุณมีเวลาข้ามไป 2 แล้วทำซ้ำตามที่ต้องการเพื่อให้สามารถจัดการรหัสได้

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

ฉันสามารถเพิ่ม:

0: ซื้อหนังสือของ Michael Feathersเกี่ยวกับการทำงานกับรหัสดั้งเดิม

โชคไม่ดีที่งานประเภทนี้เป็นเรื่องธรรมดาเกินไป แต่ประสบการณ์ของฉันคือมีคุณค่าอย่างมากในการที่สามารถทำงานได้


2

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

หากคุณระบุวิธีแก้ปัญหาที่ใช้การได้ให้ทำการรีแฟคเตอร์แอปพลิเคชันใหม่

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


+1 - เขียนใหม่ในเวลาของคุณเอง
จอนแบล็ก

2

0.05 ยูโรของฉัน:

ออกแบบระเบียบใหม่ทั้งหมดแบ่งเป็นระบบย่อยโดยคำนึงถึงความต้องการด้านเทคนิคและธุรกิจ (= หลาย ๆ แทร็กการบำรุงรักษาแบบขนานที่มีรหัสฐานที่แตกต่างกันสำหรับแต่ละคน

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

การออกแบบใหม่จะต้องเป็นโครงการแยกต่างหากการพยายามทำให้เป้าหมายเคลื่อนที่ไม่ทำงาน

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

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

นี่คือวิธีที่ฉันจะเริ่มแก้ไขปัญหาอย่างน้อยที่สุด


2

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



2

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


2

ฉันพบว่าประโยคนี้เป็นส่วนที่น่าสนใจที่สุดของโพสต์ของคุณ:

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

อันดับแรกฉันขอแนะนำให้คุณใช้ระบบควบคุมแหล่งที่มาสำหรับการพัฒนาเวอร์ชันการบำรุงรักษา 10 + ซึ่งสนับสนุนการแยกสาขา

ประการที่สองฉันจะสร้างสิบสาขา (หนึ่งสำหรับแต่ละรุ่นบำรุงรักษาของคุณ)

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

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

ฉันจะเป็นกังวลเล็กน้อยว่าคุณมีมากในฟังก์ชั่น main () ของคุณ

ในโครงการใด ๆ ที่ฉันเขียนฉันจะใช้ main () เท่านั้นดำเนินการเริ่มต้นของวัตถุหลัก - เช่นการจำลองหรือวัตถุประยุกต์ - ชั้นเรียนเหล่านี้เป็นที่ทำงานจริงควรไป

ฉันจะเริ่มต้นวัตถุบันทึกแอปพลิเคชันในหลักเพื่อใช้ทั่วโลกตลอดทั้งโปรแกรม

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

คุณพูดอย่างนั้น

> ไฟล์นั้นมี "คลาสหลัก" (การมอบหมายงานภายในและการประสานงานหลัก) ของโปรแกรมของเรา

ดูเหมือนว่างานทั้งสองนี้สามารถแบ่งออกเป็นสองวัตถุที่แยกกัน - ผู้ประสานงานและผู้มอบหมายงาน

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

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


อย่างที่ฉันเข้าใจปัญหาคือ: ถ้าคุณกัดกระสุนและ refactor คุณจะไม่สามารถพกปะระหว่างรุ่นได้อีก SCC อาจถูกติดตั้งอย่างสมบูรณ์แบบ
peterchen

@ peterchen - ปัญหาแน่นอน SCC ทำการผสานกับระดับไฟล์ (การรวม 3 ทาง) หากคุณย้ายรหัสไปมาระหว่างไฟล์ต่างๆคุณจะต้องเริ่มทำการดัดแปลงบล็อกโค้ดด้วยตนเองจากไฟล์หนึ่งไปยังอีกไฟล์หนึ่ง (ใน GIT คนคุณลักษณะอื่น ๆ ที่กล่าวถึงในความคิดเห็นอื่นเป็นเพียงที่ดีสำหรับประวัติไม่ได้สำหรับการรวมเท่าที่ผมสามารถบอกได้)
มาร์ตินบา

2

ดังที่คุณได้อธิบายไปแล้วปัญหาหลักคือการกระจายก่อนแบ่งและหลังแยกรวมในการแก้ไขข้อบกพร่อง ฯลฯ เครื่องมือรอบ ๆ มันใช้เวลาไม่นานนักในการ hardcode สคริปต์ใน Perl, Ruby และอื่น ๆ เพื่อตัดทอนเสียงส่วนใหญ่จากการแบ่ง pre-split กับการเรียงต่อกันของ post-split ทำสิ่งที่ง่ายที่สุดในแง่ของการจัดการกับเสียงรบกวน:

  • ลบบางบรรทัดก่อน / ระหว่างการต่อข้อมูล (เช่นมียาม)
  • ลบสิ่งอื่น ๆ จากเอาท์พุท diff ถ้าจำเป็น

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


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

2
+/- 0 - จริงจังคนที่คุณอาศัยอยู่ที่คุณแนะนำให้ออกจากงานขึ้นอยู่กับรายละเอียดทางเทคนิคเช่นนี้?
Martin Ba

1

"ไฟล์นั้นมี" คลาสหลัก "(การมอบหมายงานภายในและการประสานงานหลัก) ของโปรแกรมของเราดังนั้นทุกครั้งที่มีการเพิ่มฟีเจอร์มันจะมีผลกับไฟล์นี้และทุกครั้งที่มันขยาย"

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

    // declaration
    std::map<ID, ICommand*> dispatchTable;
    ...

    // populating using some loader
    dispatchTable[id] = concreteCommand;

    ...
    // using
    dispatchTable[id]->Execute();

2
ไม่ไม่มีสวิตช์ขนาดใหญ่จริง ๆ ประโยคที่เป็นเพียงที่ใกล้เคียงที่สุดที่ฉันสามารถมาอธิบายระเบียบนี้ :)
มาร์ตินบา

1

ฉันคิดว่าวิธีที่ง่ายที่สุดในการติดตามประวัติของแหล่งที่มาเมื่อแยกไฟล์จะเป็นดังนี้:

  1. ทำสำเนาของซอร์สโค้ดต้นฉบับโดยใช้คำสั่ง copy ที่สงวนประวัติไว้ในระบบ SCM ของคุณ คุณอาจจะต้องส่งมาถึงตอนนี้ แต่ยังไม่จำเป็นต้องบอกระบบบิลด์ของคุณเกี่ยวกับไฟล์ใหม่ดังนั้นมันก็โอเค
  2. ลบรหัสจากสำเนาเหล่านี้ ที่ไม่ควรทำลายประวัติศาสตร์สำหรับบรรทัดที่คุณเก็บไว้

"การใช้คำสั่งคัดลอกเก็บประวัติใด ๆ ที่ระบบ SCM ของคุณมอบให้" ... สิ่งเลวร้ายที่มันไม่ได้จัดเตรียมไว้ให้เลย
Martin Ba

เลวร้ายเกินไป. เพียงอย่างเดียวฟังดูเหมือนเป็นเหตุผลที่ดีที่จะเปลี่ยนมาใช้สิ่งที่ทันสมัยกว่า :-)
Christopher Creutzig

1

ฉันคิดว่าสิ่งที่ฉันจะทำในสถานการณ์นี้คือสัญลักษณ์แสดงหัวข้อย่อยและ:

  1. คิดออกว่าฉันต้องการแยกไฟล์อย่างไร (ตามเวอร์ชันการพัฒนาปัจจุบัน)
  2. ใส่ล็อกการดูแลระบบไว้ในไฟล์ ("ไม่มีใครแตะ mainmodule.cpp หลังเวลา 17.00 น. วันศุกร์ !!!"
  3. ใช้ช่วงวันหยุดยาวของคุณในการใช้งานซึ่งเปลี่ยนเป็นรุ่นบำรุงรักษา> 10 (ตั้งแต่เก่าที่สุดไปจนถึงใหม่ล่าสุด) จนถึงและรวมถึงเวอร์ชันปัจจุบัน
  4. ลบ mainmodule.cpp ออกจากซอฟต์แวร์เวอร์ชั่นที่รองรับทั้งหมด มันเป็นยุคใหม่ - ไม่มี mainmodule.cpp อีกต่อไป
  5. โน้มน้าวฝ่ายบริหารว่าคุณไม่ควรสนับสนุนซอฟต์แวร์บำรุงรักษามากกว่าหนึ่งรุ่น (อย่างน้อยไม่มีสัญญาการสนับสนุน $$$ ขนาดใหญ่) หากลูกค้าของคุณแต่ละคนมีรุ่นที่เป็นเอกลักษณ์ของตัวเอง .... yeeeeeshhhh ฉันจะเพิ่มคำสั่งคอมไพเลอร์มากกว่าพยายามรักษา 10+ ส้อม

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

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