การปรับโครงสร้างใหม่ - เหมาะสมหรือไม่ที่จะเขียนรหัสใหม่อีกครั้งตราบใดที่การทดสอบทั้งหมดผ่าน?


9

ฉันเพิ่งดู"ทุกสิ่งเล็ก ๆ น้อย ๆ "จาก RailsConf 2014 ในระหว่างการพูดคุยนี้ Sandi Metz refactors ฟังก์ชั่นที่รวมถึงคำสั่ง if ซ้อนขนาดใหญ่:

def tick
    if @name != 'Aged Brie' && @name != 'Backstage passes to a TAFKAL80ETC concert'
        if @quality > 0
            if @name != 'Sulfuras, Hand of Ragnaros'
                @quality -= 1
            end
        end
    else
        ...
    end
    ...
end

ขั้นตอนแรกคือการแบ่งฟังก์ชั่นออกเป็นหลาย ๆ อันเล็ก ๆ :

def tick
    case name
    when 'Aged Brie'
        return brie_tick
    ...
    end
end

def brie_tick
    @days_remaining -= 1
    return if quality >= 50

    @quality += 1
    @quality += 1 if @days_remaining <= 0
end

สิ่งที่ฉันคิดว่าน่าสนใจคือวิธีที่ฟังก์ชั่นเล็ก ๆ เหล่านี้เขียนขึ้น brie_tickตัวอย่างเช่นไม่ได้เขียนโดยการแยกส่วนที่เกี่ยวข้องของtickฟังก์ชั่นดั้งเดิมแต่เริ่มต้นจากการอ้างอิงถึงการtest_brie_*ทดสอบหน่วย เมื่อผ่านการทดสอบหน่วยเหล่านี้แล้วbrie_tickก็ถือว่าเสร็จสิ้น เมื่อฟังก์ชั่นขนาดเล็กทั้งหมดเสร็จสิ้นแล้วtickฟังก์ชั่นเสาหินเดิมจะถูกลบ

น่าเสียดายที่ผู้นำเสนอดูเหมือนไม่ทราบว่าวิธีการนี้นำไปสู่การสามในสี่*_tickหน้าที่ผิด (และอีกอันว่างเปล่า!) มีกรณีขอบซึ่งพฤติกรรมของ*_tickฟังก์ชั่นที่แตกต่างจากtickฟังก์ชั่นเดิม ยกตัวอย่างเช่น@days_remaining <= 0ในbrie_tickควรจะเป็น< 0- เพื่อให้brie_tickไม่สามารถทำงานได้อย่างถูกต้องเมื่อเรียกด้วย และdays_remaining == 1quality < 50

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


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

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

@JohnWu - ฉันรู้สึกว่า refactoring นั้นถูกทำขึ้นเป็นชุดของการแปลงเล็ก ๆ เป็นซอร์สโค้ด ("แยกวิธีการ" ฯลฯ ) แทนที่จะใช้เพียงแค่เขียนรหัสใหม่ (โดยที่ฉันหมายถึงการเขียนอีกครั้งโดยไม่มีแม้แต่ ดูรหัสที่มีอยู่ตามที่ทำในงานนำเสนอที่เชื่อมโยง)
user200783

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

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

คำตอบ:


11

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

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

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

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


1
ขอบคุณที่ทำให้รู้สึก ดังนั้นหากทางออกที่ดีที่สุดในการเปลี่ยนแปลงพฤติกรรมที่ไม่พึงประสงค์คือการทดสอบที่ครอบคลุมจะมีวิธีใดที่จะมั่นใจได้ว่าการทดสอบครอบคลุมทุกกรณีที่เป็นไปได้? ยกตัวอย่างเช่นมันจะเป็นไปได้ที่จะมีความคุ้มครอง 100% ของbrie_tickในขณะที่ยังไม่เคยทดสอบปัญหา@days_remaining == 1กรณีโดยยกตัวอย่างเช่นการทดสอบกับ@days_remainingชุดและ10 -10
user200783

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

1
ในกรณีนี้กิ่งที่ไม่ได้รับอาจถูกจับด้วยเครื่องมือครอบคลุมรหัสในขณะที่การพัฒนาการทดสอบ
cbojar

2

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

สิ่งหนึ่งที่ยากมากเกี่ยวกับการทำงานกับรหัสดั้งเดิมคือการได้รับความเข้าใจที่สมบูรณ์เกี่ยวกับพฤติกรรมปัจจุบัน

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

จากการพูดคุย :

ตอนนี้เป็นการปรับโครงสร้างใหม่ตามคำจำกัดความของการปรับโครงสร้างใหม่ ฉันจะ refactor รหัสนี้ ฉันจะเปลี่ยนการจัดเรียงโดยไม่เปลี่ยนพฤติกรรมของมัน

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

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

หรือความล้มเหลวของการเปลี่ยนโครงสร้าง - เนื่องจากรหัสควรได้รับการแปลงทีละขั้นตอนแทนที่จะเขียนใหม่ตั้งแต่ต้น?

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

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

ดังนั้นฉันคิดว่าเพื่อนร่วมงานของคุณได้รับการคัดเลือกไม่ดี ไม่ANDOR


2

การสร้างใหม่ไม่ควรเปลี่ยนพฤติกรรมที่มองเห็นได้จากภายนอกของรหัสของคุณ นั่นคือเป้าหมาย

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

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


1

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

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

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