อะไรจะช่วยได้เมื่อทำการเปลี่ยนวิธีการขนาดใหญ่เพื่อให้แน่ใจว่าฉันจะไม่ทำลายอะไรเลย?


10

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

โปรดทราบว่าการเปลี่ยนโครงสร้างรวมถึงการย้ายรหัส C # ดั้งเดิมไปสู่รูปแบบการทำงานที่มากขึ้น (รหัสดั้งเดิมไม่ได้ใช้คุณสมบัติใด ๆ ของ. NET Framework 3 และใหม่กว่ารวมถึง LINQ) การเพิ่มข้อมูลทั่วไปที่รหัสอาจได้รับประโยชน์จากพวกเขาเป็นต้น

ฉันไม่สามารถใช้วิธีการที่เป็นทางการได้เพราะจะมีค่าใช้จ่ายเท่าใด

ในทางกลับกันฉันคิดว่าอย่างน้อย"กฎใด ๆ ที่ได้รับการปรับเปลี่ยนใหม่จะต้องมาพร้อมกับการทดสอบหน่วย"ควรปฏิบัติตามกฎอย่างเคร่งครัดไม่ว่าจะต้องเสียค่าใช้จ่ายเท่าใด ปัญหาคือเมื่อฉันสร้างส่วนเล็ก ๆ ของวิธีส่วนตัว 500 LOC การเพิ่มการทดสอบหน่วยดูเหมือนจะเป็นงานที่ยาก

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

  • รู้ว่าฉันควรสร้างการทดสอบหน่วยใด

  • และ / หรือทราบว่าการเปลี่ยนแปลงที่ฉันทำมีผลกับรหัสต้นฉบับในแบบที่มันทำงานแตกต่างจากนี้หรือไม่?


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

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

1
คุณจะไม่ต้องการใช้formal methods in software developmentอยู่ดีเพราะมันถูกใช้เพื่อพิสูจน์ความถูกต้องของโปรแกรมโดยใช้ตรรกะของภาคแสดงและจะไม่มีประโยชน์ในการปรับโครงสร้างฐานข้อมูลขนาดใหญ่อีกครั้ง โดยทั่วไปแล้ววิธีการทางการจะใช้เพื่อพิสูจน์รหัสทำงานอย่างถูกต้องในพื้นที่เช่นการใช้งานทางการแพทย์ คุณถูกต้องมันมีค่าใช้จ่ายสูงซึ่งเป็นสาเหตุที่ไม่ได้ใช้บ่อย
Mushy

เครื่องมือที่ดีเช่นตัวเลือก refactor ในReSharperทำให้งานนั้นง่ายขึ้นมาก ในสถานการณ์เช่นนี้มันคุ้มค่าเงิน
billy.bob

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

คำตอบ:


12

ฉันมีความท้าทายที่คล้ายกัน หนังสือWorking with Legacy Codeเป็นแหล่งข้อมูลที่ยอดเยี่ยม แต่มีข้อสันนิษฐานว่าคุณสามารถใช้ฐานเสียงในการทดสอบหน่วยเพื่อสนับสนุนงานของคุณ บางครั้งก็เป็นไปไม่ได้

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

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

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

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

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


2
+1 สำหรับการใช้ "โบราณคดี" เพียงอย่างเดียว นั่นเป็นคำเดียวกันกับที่ฉันใช้เพื่ออธิบายกิจกรรมนี้และฉันคิดว่านั่นเป็นวิธีที่ดีในการใส่ (เช่นกันคำตอบก็ดี - ฉันไม่ได้ตื้นจริงๆ)
Erik Dietrich

10

อะไรจะช่วยได้เมื่อทำการเปลี่ยนวิธีการขนาดใหญ่เพื่อให้แน่ใจว่าฉันจะไม่ทำลายอะไรเลย?

คำตอบสั้น ๆ : ก้าวเล็ก ๆ

ปัญหาคือเมื่อฉันสร้างส่วนเล็ก ๆ ของวิธีส่วนตัว 500 LOC การเพิ่มการทดสอบหน่วยดูเหมือนจะเป็นงานที่ยาก

พิจารณาขั้นตอนเหล่านี้:

  1. ย้ายการใช้ไปสู่ฟังก์ชั่นอื่น (ส่วนตัว) และมอบหมายการโทร

    // old:
    private int ugly500loc(int parameters) {
        // 500 LOC here
    }
    
    // new:    
    private int ugly500loc_old(int parameters) {
        // 500 LOC here
    }
    
    private void ugly500loc(int parameters) {
        return ugly500loc_old(parameters);
    }
    
  2. เพิ่มรหัสการบันทึก (ตรวจสอบให้แน่ใจว่าการบันทึกไม่ได้ล้มเหลว) ในฟังก์ชั่นดั้งเดิมของคุณสำหรับอินพุตและเอาต์พุตทั้งหมด

    private void ugly500loc(int parameters) {
        static int call_count = 0;
        int current = ++call_count;
        save_to_file(current, parameters);
        int result = ugly500loc_old(parameters);
        save_to_file(current, result); // result, any exceptions, etc.
        return result;
    }
    

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

  3. ตอนนี้คุณมีmax(call_count)ชุดของอินพุตและเอาต์พุตเพื่อเขียนการทดสอบของคุณด้วย; คุณสามารถเขียนการทดสอบเดียวที่วนซ้ำพารามิเตอร์ / ชุดผลลัพธ์ทั้งหมดที่คุณมีและดำเนินการทดสอบแบบวนซ้ำ นอกจากนี้คุณยังสามารถเขียนแบบทดสอบ aditional ที่เรียกใช้ชุดค่าผสมเฉพาะ (เพื่อใช้ในการตรวจสอบการส่งผ่านชุด i / o ได้อย่างรวดเร็ว)

  4. ย้าย// 500 LOC hereกลับไปที่ugly500locฟังก์ชันของคุณ(และลบฟังก์ชันการบันทึก)

  5. เริ่มแยกฟังก์ชั่นจากฟังก์ชั่นใหญ่ (ไม่ทำอะไรเลยเพียงแยกฟังก์ชั่น) แล้วทำการทดสอบ หลังจากนี้คุณควรมีฟังก์ชั่นเพิ่มเติมเล็กน้อยในการ refactor แทนที่จะเป็น 500LOC

  6. ใช้ชีวิตอย่างมีความสุขตลอดไป


3

โดยปกติการทดสอบหน่วยเป็นวิธีที่จะไป

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

สิ่งใดที่สามารถช่วยฉันในการรู้ว่าการทดสอบหน่วยใดที่เกี่ยวข้องกับโค้ดบางส่วน

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

จากนั้นคุณสามารถฉีกขาดออกจากกันโดยไม่มีปัญหาใด ๆ

AFAIK ไม่มีเทคนิคกันกระสุนสำหรับสิ่งนี้ ... คุณเพียงแค่ต้องมีระเบียบ (ไม่ว่าคุณจะรู้สึกสบายใจในเรื่องใด) เวลาและความอดทนมากมาย! :)

ไชโยและขอให้โชคดี!

อเล็กซ์


เครื่องมือครอบคลุมรหัสมีความสำคัญที่นี่ การยืนยันว่าคุณครอบคลุมทุกเส้นทางด้วยวิธีการที่ซับซ้อนขนาดใหญ่ผ่านการตรวจสอบเป็นเรื่องยาก เครื่องมือที่แสดงให้เห็นว่า KitchenSinkMethodTest01 () ... KitchenSinkMethodTest17 () ครอบคลุมบรรทัดที่ 1-45, 48-220, 245-399 และ 488-500 แต่อย่าแตะรหัสระหว่าง; จะทำให้การทดสอบสิ่งที่เพิ่มเติมที่คุณต้องเขียนง่ายขึ้นมาก
Dan Is Fiddling โดย Firelight
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.