ฉันจะหลีกเลี่ยงการทำรีดักชั่นแบบเรียงซ้อนได้อย่างไร?


52

ฉันมีโครงการแล้ว ในโครงการนี้ฉันต้องการ refactor เพื่อเพิ่มคุณสมบัติและฉัน refactored โครงการเพื่อเพิ่มคุณสมบัติ

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

ฉันจะหลีกเลี่ยงการปรับลดประเภทของการเรียงซ้อนในอนาคตได้อย่างไร มันเป็นแค่อาการของชั้นเรียนก่อนหน้าของฉันซึ่งขึ้นอยู่กับแต่ละคนแน่นเกินไปหรือไม่?

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

แก้ไขที่ใหญ่กว่าที่ฉันสัญญาห้าวันที่ผ่านมา:

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

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

for(auto&& a : as) {
     f(a);
}

อย่างไรก็ตามเพื่อให้ได้บริบทนี้ฉันต้องเปลี่ยนมันเป็นอะไรที่มากกว่า

std::vector<Context> contexts;
for(auto&& a : as)
    contexts.push_back(g(a));
do_thing_now_we_have_contexts();
for(auto&& con : contexts)
    f(con);

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

และนั่นคือวิธีที่ฉันสิ้นสุดที่ฉันอยู่ตอนนี้ เหตุผลเดียวที่ฉันยังคงทำต่อไปก็เพราะฉันต้องการการรีแฟคเตอร์นี้ด้วยเหตุผลอื่น ๆ


67
เมื่อคุณพูดว่า "refactored โครงการเพื่อเพิ่มคุณสมบัติ" คุณหมายถึงอะไรกันแน่? การเปลี่ยนโครงสร้างไม่ได้เปลี่ยนลักษณะการทำงานของโปรแกรมตามคำจำกัดความซึ่งทำให้คำสั่งนี้สับสน
Jules

5
@Jules: พูดอย่างเคร่งครัดคุณลักษณะคืออนุญาตให้นักพัฒนารายอื่นเพิ่มประเภทเฉพาะของส่วนขยายดังนั้นคุณลักษณะจึงเป็น refactor ซึ่งทำให้โครงสร้างคลาสเปิดขึ้น
DeadMG

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

4
@DeadMG: นี่คือหนังสือที่ฉันต้องการอ้างถึงในความคิดเห็นแรกของฉัน: "เกม" pick-up sticks "เป็นคำเปรียบเทียบที่ดีสำหรับวิธี Mikado คุณกำจัด" หนี้ทางเทคนิค "- ปัญหาดั้งเดิมที่ฝังอยู่ในซอฟต์แวร์เกือบทุกตัว ระบบ - โดยทำตามชุดของกฎที่ง่ายต่อการนำไปใช้คุณแยกข้อมูลการพึ่งพาแต่ละรายการเข้าด้วยกันอย่างระมัดระวังจนกว่าคุณจะพบปัญหาส่วนกลางโดยไม่ยุบโครงการ "

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

คำตอบ:


69

ครั้งสุดท้ายที่ฉันพยายามที่จะเริ่มต้น refactoring กับผลกระทบที่ไม่คาดฝันและฉันไม่สามารถรักษาเสถียรภาพของการสร้างและ / หรือทดสอบหลังจากที่ วันหนึ่งผมให้ขึ้นและหวนกลับ codebase ไปยังจุดก่อนการ refactoring

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

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

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


53

มันเป็นแค่อาการของชั้นเรียนก่อนหน้าของฉันซึ่งขึ้นอยู่กับแต่ละคนแน่นเกินไปหรือไม่?

แน่ใจ การเปลี่ยนแปลงหนึ่งอย่างที่ทำให้เกิดการเปลี่ยนแปลงอื่น ๆ มากมายคือนิยามของการแต่งงานกัน

ฉันจะหลีกเลี่ยง refactors แบบเรียงซ้อนได้อย่างไร

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

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


33
+1 ยิ่งต้องทำการปรับโครงสร้างอีกครั้ง มันเป็นธรรมชาติของสิ่งนั้น
พอลเดรเปอร์

4
หากคุณทำการปรับโครงสร้างใหม่อย่างแท้จริงรหัสอื่น ๆ ไม่ควรกังวลกับการเปลี่ยนแปลงในทันที (แน่นอนที่สุดคุณจะต้องการล้างส่วนอื่น ๆ ... แต่ไม่ควรทำทันที) การเปลี่ยนแปลงที่ "ลดหลั่น" ผ่านส่วนที่เหลือของแอปใหญ่กว่าการปรับโครงสร้างใหม่ - ณ จุดนั้น โดยทั่วไปการออกแบบหรือเขียนใหม่
cHao

+1 อะแดปเตอร์เป็นวิธีแยกรหัสที่คุณต้องการเปลี่ยนอย่างแน่นอน
วิงค์เบรซ

17

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

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


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

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

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

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

26
@DeadMG ในกรณีเช่นนี้โดยปกติคุณจะต้องพัฒนาฟีเจอร์ใหม่เพิ่มการทดสอบเพื่อให้แน่ใจว่าใช้งานได้เปลี่ยนรหัสที่มีอยู่เพื่อใช้อินเทอร์เฟซใหม่แล้วลบฟีเจอร์เก่าที่ฟุ่มเฟือย (ตอนนี้) ด้วยวิธีนี้ไม่ควรมีจุดที่สิ่งต่าง ๆ แตกหัก
sapi

12

ฉันจะหลีกเลี่ยง refactor แบบลดหลั่นชนิดนี้ในอนาคตได้อย่างไร

การออกแบบการคิดอย่างปรารถนา

เป้าหมายคือการออกแบบ OO ที่ยอดเยี่ยมและการใช้งานสำหรับคุณสมบัติใหม่ การหลีกเลี่ยงการปรับโครงสร้างซ้ำก็เป็นเป้าหมายเช่นกัน

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

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

  • Refactor เท่านั้นที่จะทำให้ตะเข็บที่จำเป็นในการฉีด / ใช้รหัสคุณลักษณะใหม่
    • ความต้านทานต่อการเปลี่ยนโครงสร้างไม่ควรผลักดันการออกแบบใหม่
  • เขียนไคลเอนต์ที่เรียนด้วย API ที่ทำให้ฟีเจอร์ใหม่ & โคเดซที่มีอยู่นั้นหลงลืมกันและกันอย่างมีความสุข
    • มันแปลเพื่อรับวัตถุข้อมูลและผลลัพธ์ไปมา หลักการความรู้ขั้นต่ำต้องถูกสาป เราจะไม่ทำอะไรที่แย่ไปกว่าโค้ดที่มีอยู่แล้ว

การเรียนรู้ด้วยบทเรียนการเรียนรู้ ฯลฯ

การปรับโครงสร้างใหม่นั้นง่ายพอ ๆ กับการเพิ่มพารามิเตอร์เริ่มต้นให้กับการเรียกเมธอดที่มีอยู่ หรือการเรียกครั้งเดียวไปยังวิธีการเรียนแบบคงที่

วิธีการขยายชั้นเรียนที่มีอยู่สามารถช่วยรักษาคุณภาพการออกแบบใหม่โดยมีความเสี่ยงน้อยที่สุด

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

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

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


9

จากหนังสือ (ที่ยอดเยี่ยม) การทำงานอย่างมีประสิทธิภาพด้วยรหัสมรดกโดย Michael Feathers :

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

หากภายหลังคุณสามารถครอบคลุมรหัสรอบจุดที่คุณทำลายการพึ่งพาคุณสามารถรักษารอยแผลเป็นนั้นได้เช่นกัน


6

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

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

เป็นการยากที่จะให้คำแนะนำเฉพาะโดยไม่มีรหัสเฉพาะ


3

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

ฉันขอแนะนำหนังสือเหล่านี้ด้วย

  • ทำงานอย่างมีประสิทธิภาพด้วยรหัสมรดกขน
  • Refactoring , Fowler
  • การพัฒนาซอฟต์แวร์เชิงวัตถุนำโดยการทดสอบฟรีแมนและไพรซ์
  • ทำความสะอาดรหัสมาร์ติน

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

6
ถ้าฉันกำลัง refactoring อินเทอร์เฟซที่ใช้งานหนักฉันเพิ่ม shim ชิมนี้จัดการการเริ่มต้นเพื่อให้การโทรแบบเดิมยังคงทำงานต่อไป ฉันทำงานบนอินเทอร์เฟซที่อยู่เบื้องหลัง shim จากนั้นเมื่อฉันทำเสร็จฉันเริ่มเปลี่ยนคลาสเพื่อใช้อินเตอร์เฟสอีกครั้งแทน shim
asthasr

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

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

3
@DeadMG ในกรณีนี้ฉันคิดว่าสิ่งที่คุณทำไม่สามารถเรียกได้ว่า refactoring เป็นจุดพื้นฐานที่จะนำการเปลี่ยนแปลงการออกแบบมาใช้เป็นชุดของขั้นตอนง่าย ๆ
Jules

3

มันเป็นแค่อาการของชั้นเรียนก่อนหน้าของฉันซึ่งขึ้นอยู่กับแต่ละคนแน่นเกินไปหรือไม่?

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

ฉันจะหลีกเลี่ยงการปรับลดประเภทของการเรียงซ้อนในอนาคตได้อย่างไร

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

วิธีการนั้นมีชื่อว่า "Mikado Method" และทำงานดังนี้:

  1. เขียนวัตถุประสงค์ที่คุณต้องการบรรลุลงบนกระดาษ

  2. ทำการเปลี่ยนแปลงที่ง่ายที่สุดที่จะนำคุณไปสู่ทิศทางนั้น

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

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

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

  6. เลือกหนึ่งในภารกิจที่ไม่มีข้อผิดพลาดขาออก (ไม่ทราบการอ้างอิง) และกลับไปที่ 2

  7. ส่งการเปลี่ยนแปลงข้ามงานบนกระดาษเลือกงานที่ไม่มีข้อผิดพลาดขาออก (ไม่ทราบการอ้างอิง) และกลับไปที่ 2

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


2

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

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


2

... ฉันปรับโครงสร้างโครงการอีกครั้งเพื่อเพิ่มคุณสมบัติ

@Jules ดังที่กล่าวไว้การสร้างใหม่และการเพิ่มคุณสมบัติเป็นสองสิ่งที่แตกต่างกันมาก

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

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

ฉันจำเป็นต้องทำการเปลี่ยนแปลงอินเตอร์เฟสเล็กน้อยเพื่อรองรับ

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

ดังนั้นคลาสการบริโภคไม่สามารถนำไปใช้กับอินเทอร์เฟซปัจจุบันในแง่ของอินเทอร์เฟซใหม่ดังนั้นจึงจำเป็นต้องมีอินเทอร์เฟซใหม่เช่นกัน

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


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

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


-1

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


1
นี้ไม่ได้ดูเหมือนจะนำเสนออะไรที่สำคัญกว่าจุดทำและอธิบายในก่อน 9 คำตอบ
ริ้น

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