วิธีดูแลรักษาซอฟต์แวร์รุ่นเดียวกันที่แตกต่างและกำหนดเองสำหรับลูกค้าหลายราย


46

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

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

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


4
ไม่#ifdefทำงานสำหรับคุณ?
ohho

6
คำสั่งของโปรเซสเซอร์ล่วงหน้าสามารถซับซ้อนได้อย่างรวดเร็วและทำให้โค้ดอ่านยากขึ้นและยากขึ้นในการดีบัก
Falcon

1
คุณควรรวมรายละเอียดเกี่ยวกับแพลตฟอร์มจริงและประเภทของแอปพลิเคชัน (เดสก์ท็อป / เว็บ) เพื่อคำตอบที่ดีกว่า การปรับแต่งในแอปเดสก์ท็อป C ++ นั้นแตกต่างจากแอปพลิเคชันเว็บ PHP อย่างสิ้นเชิง
GrandmasterB

2
@ เหยี่ยว: ตั้งแต่คุณเลือกคำตอบในปี 2011 ฉันมีข้อสงสัยมากมายคุณช่วยบอกเราได้ไหมว่าคุณมีประสบการณ์ในการใช้ SVN ในวิธีที่แนะนำในระหว่างนี้หรือไม่? คำคัดค้านของฉันไม่ดีหรือไม่?
Doc Brown

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

คำตอบ:


7

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

http://svnbook.red-bean.com/en/1.5/svn.branchmerge.commonpatterns.html

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

จากนั้นคุณก็มีสาขาเฉพาะลูกค้าของคุณที่คุณรวมสาขาของฟีเจอร์เดียวกันตามที่ต้องการ

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

กิ่งก้านสาขาเฉพาะลูกค้าเป็นกิ่งก้านสาขาขนานกับลำตัวและมีการใช้งานตราบเท่าที่ลำตัวมันเองและไม่ได้รวมกันที่ใดก็ได้

            feature1
            ———————————.
                        \
trunk                    \
================================================== · · ·
      \ client1            \
       `========================================== · · ·
        \ client2            \
         `======================================== · · ·
              \ client2-specific feature   /
               `——————————————————————————´

7
+1 สำหรับการแยกฟีเจอร์ คุณสามารถใช้สาขาสำหรับลูกค้าแต่ละรายได้เช่นกัน ฉันอยากจะแนะนำเพียงสิ่งเดียว: ใช้ VCS แบบกระจาย (hg, git, bzr) แทน SVN / CVS เพื่อให้บรรลุสิ่งนี้;)
Herberth Amaral

42
-1 นั่นไม่ใช่สิ่งที่ "สาขาฟีเจอร์" มีไว้สำหรับ; มันขัดแย้งกับคำจำกัดความของพวกเขาในฐานะ "สาขาชั่วคราวที่ผสานเมื่อการพัฒนาคุณลักษณะสิ้นสุดลง"
P Shved

13
คุณยังต้องรักษา deltas เหล่านั้นทั้งหมดในการควบคุมแหล่งที่มาและทำให้พวกเขาเรียงราย - ตลอดไป ในระยะสั้นสิ่งนี้อาจใช้ได้ ในระยะยาวมันอาจจะแย่
quick_now

4
-1 นี่ไม่ใช่ปัญหาการตั้งชื่อ - การใช้กิ่งต่าง ๆ สำหรับไคลเอนต์ที่แตกต่างกันเป็นเพียงรูปแบบการทำสำเนารหัสอื่น นี่คือรูปแบบการต่อต้าน IMHO ตัวอย่างวิธีที่จะไม่ทำ
Doc Brown

6
โรเบิร์ตฉันคิดว่าฉันเข้าใจได้ดีในสิ่งที่คุณแนะนำแม้กระทั่งก่อนการแก้ไข แต่ฉันคิดว่านี่เป็นวิธีที่น่ากลัว สมมติว่าคุณมีไคลเอนต์ N ทุกครั้งที่คุณเพิ่มฟีเจอร์ core ใหม่ลงใน trunk ดูเหมือนว่า SCM จะทำให้ง่ายต่อการเผยแพร่ฟีเจอร์ใหม่ไปยังสาขา N แต่การใช้สาขาด้วยวิธีนี้ทำให้ง่ายเกินไปที่จะหลีกเลี่ยงการแยกการแก้ไขเฉพาะไคลเอ็นต์ เป็นผลให้ตอนนี้คุณมีโอกาส N ที่จะได้รับความขัดแย้งผสานสำหรับการเปลี่ยนแปลงแต่ละครั้งในลำตัว นอกจากนี้ตอนนี้คุณต้องรันการทดสอบการรวม N แทนการทดสอบหนึ่ง
Doc Brown

37

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

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


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

ถ้าฉันไม่พลาดบางสิ่งที่คุณแนะนำคือถ้าคุณมีลูกค้า 100 คนคุณจะสร้าง (100 x NumberOfChangedProjects ) และใช้ DI เพื่อจัดการพวกเขา? ถ้าเป็นเช่นนั้นผม definetely จะอยู่ห่างจากชนิดของการแก้ปัญหาตั้งแต่การบำรุงรักษานี้จะเป็นที่น่ากลัว ..
sotn

12

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

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

หากคุณไม่ชอบifdefให้ใช้ "ส่วนต่อประสานและการใช้งาน", "functors", "object files" หรือเครื่องมืออื่น ๆ ที่ภาษาของคุณใช้สำหรับเก็บสิ่งต่าง ๆ ไว้ในที่เดียว

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


8

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


5

อย่างระมัดระวัง

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

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

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

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

ในระยะสั้น:

  1. ระบบโมดูลาร์หนักที่มีโครงสร้างโครงการแบน
  2. สร้างโครงการสำหรับแต่ละโปรไฟล์กำหนดค่า (ลูกค้าแม้ว่าจะมีมากกว่าหนึ่งสามารถแชร์โปรไฟล์)
  3. รวบรวมชุดการทำงานที่จำเป็นเป็นผลิตภัณฑ์ที่แตกต่างและปฏิบัติต่อเช่นนั้น

หากทำอย่างถูกต้องชุดผลิตภัณฑ์ของคุณควรมีไฟล์การกำหนดค่าทั้งหมด แต่ไม่กี่ไฟล์

หลังจากใช้สิ่งนี้ไปซักพักผมก็ลงเอยด้วยการสร้างเมตาแพ็คเกจซึ่งรวบรวมส่วนใหญ่ที่ใช้หรือระบบที่จำเป็นเป็นหน่วยหลักและใช้เมตาแพคเกจนี้สำหรับการประกอบลูกค้า หลังจากไม่กี่ปีที่ผ่านมาฉันมีกล่องเครื่องมือขนาดใหญ่ที่ฉันสามารถรวบรวมได้อย่างรวดเร็วเพื่อสร้างโซลูชันลูกค้า ตอนนี้ฉันกำลังมองหาSpring Rooและดูว่าฉันไม่สามารถผลักดันความคิดนี้ต่อไปสักหน่อยได้หรือไม่ฉันหวังว่าวันหนึ่งฉันจะสามารถสร้างร่างของระบบที่เหมาะสมกับลูกค้าในการสัมภาษณ์ครั้งแรกของเรา ... ฉันเดาว่า การพัฒนา ;-)

หวังว่านี่จะช่วยได้


3

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

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

หากโมดูลของคุณถูกเข้ารหัสเช่นนั้นโดยใช้นโยบาย X1 ในโมดูล A ต้องใช้นโยบาย X2 ในโมดูล B ให้คิดถึงการปรับโครงสร้างใหม่เพื่อให้ X1 และ X2 สามารถรวมกันเป็นคลาสนโยบายเดียว


1

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


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


1

หากคุณเขียนด้วย C ธรรมดา ๆ นี่เป็นวิธีที่น่าเกลียดมาก ๆ

  • รหัสทั่วไป (เช่นหน่วย "frangulator.c")

  • รหัสเฉพาะลูกค้าชิ้นส่วนที่มีขนาดเล็กและใช้สำหรับลูกค้าแต่ละรายเท่านั้น

  • ในรหัสหน่วยหลักใช้ #ifdef และ #include เพื่อทำสิ่งที่ต้องการ

#ifdef CLIENT = CLIENTA
#include "frangulator_client_a.c"
endif #

ใช้สิ่งนี้เป็นรูปแบบซ้ำไปซ้ำมาในทุกหน่วยรหัสที่ต้องมีการปรับแต่งเฉพาะลูกค้า

นี่เป็นเรื่องที่น่าเกลียดมากและนำไปสู่ปัญหาอื่น ๆ แต่ก็ง่ายและคุณสามารถเปรียบเทียบไฟล์เฉพาะไคลเอ็นต์กับไฟล์อื่นได้อย่างง่ายดาย

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

หากคุณฉลาดจริง ๆ คุณสามารถตั้งค่า makefiles เพื่อสร้างคำนิยามไคลเอนต์ที่ถูกต้องดังนั้น:

ทำให้ลูกค้า

จะสร้างสำหรับ client_a และ "make clientb" จะสร้างสำหรับ client_b เป็นต้น

(และ "สร้าง" โดยไม่มีเป้าหมายที่ให้ไว้สามารถออกคำเตือนหรือคำอธิบายการใช้งานได้)

ฉันเคยใช้แนวคิดที่คล้ายกันมาก่อนใช้เวลาในการตั้งค่า แต่อาจมีประสิทธิภาพมาก ในกรณีของฉันหนึ่งต้นกำเนิดสร้างขึ้นประมาณ 120 ผลิตภัณฑ์ที่แตกต่าง


0

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

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

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