TDD ต้องการแนวทางในการแก้ไขปัญหาอัลกอริทึม


10

ฉันล้มเหลวในการทดสอบอัลกอริทึมกับ Codility เพราะฉันพยายามหาวิธีแก้ปัญหาที่ดีกว่าและในที่สุดฉันก็ไม่มีอะไรเลย

ดังนั้นฉันคิดว่าถ้าฉันสามารถใช้วิธีการคล้ายกับ TDD เช่นถ้าฉันสามารถพัฒนาวิธีแก้ปัญหาแบบค่อยเป็นค่อยไปในแบบเดียวกันได้หรือไม่

หากฉันกำลังเขียนอัลกอริทึมการเรียงลำดับฉันสามารถย้ายจาก Bubblesort มาตรฐานไปเป็น Bubbleort แบบ 2 ทางได้ แต่จากนั้นบางสิ่งที่ก้าวหน้ากว่าอย่าง Quicksort จะเป็น "quantum leap" แต่อย่างน้อยฉันจะมี testdata ฉันสามารถตรวจสอบได้อย่างง่ายดาย

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

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

ด้วย "Similar to TDD" ฉันหมายถึง:

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

ฉันคิดว่านี่น่าจะง่ายต่อการเข้าใจถ้าคุณเปรียบเทียบ

  1. เขียนเชลล์เรียงลำดับโดยตรง
  2. กระโดดจาก bubbleort ไปที่ quicksort (เขียนซ้ำทั้งหมด)
  3. การย้ายแบบเพิ่มขึ้นจากการเรียงฟองแบบทางเดียวเป็นการเรียงแบบเชลล์ (ถ้าเป็นไปได้)

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

"ค่อย ๆ " :-) - ดูประโยคสุดท้ายที่เพิ่ม "ดังนั้นแทน ... "
Olav

2
แน่นอนว่าคุณสามารถลองแก้ไขปัญหาได้มากมายด้วยวิธีการแก้ปัญหาการทำงาน (แต่ไม่มีประสิทธิภาพมาก) ก่อนจากนั้นจึงปรับปรุง นี่เป็นวิธีที่ไม่ จำกัด เฉพาะปัญหาอัลกอริทึมหรือการเขียนโปรแกรมและมันก็ไม่ได้มีอะไรเหมือนกันกับ TDD สิ่งนี้ตอบคำถามของคุณหรือไม่
Doc Brown

@DocBrown No - ดูตัวอย่าง Bubblesort / Quicksort TDD "ใช้งานได้ดี" เนื่องจากวิธีการที่เพิ่มขึ้นนั้นทำงานได้ดีสำหรับปัญหาหลายประเภท ปัญหา Algoritmic อาจแตกต่างกัน
Olav

ดังนั้นคุณหมายถึง "เป็นไปได้ที่จะแก้คำถามการออกแบบอัลกอริทึมในลักษณะที่เพิ่มขึ้น" (เช่นเดียวกับ TDD เป็นวิธีการที่เพิ่มขึ้น) และไม่ใช่ "โดย TDD" ใช่ไหม? กรุณาชี้แจง
Doc Brown

คำตอบ:


9

ดูความพยายามของ Ron Jeffries ในการสร้างตัวแก้ซูโดกุด้วย TDDซึ่งน่าเสียดายที่ใช้งานไม่ได้

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

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

นี่คือเหตุผลที่การศึกษาขั้นพื้นฐานในทฤษฎี CS รวมกับการฝึกเขียนโปรแกรมอัลกอริทึมจำนวนมากมีความสำคัญเท่าเทียมกัน การรู้ว่ามี "เทคนิค" (หน่วยการสร้างขนาดเล็กของอัลกอริธึม) อยู่เป็นทางยาวไปสู่การเพิ่มจำนวนควอนตัมแบบก้าวกระโดด


มีความแตกต่างที่สำคัญบางอย่างระหว่างความก้าวหน้าที่เพิ่มขึ้นในอัลกอริทึมและ TDD

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

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

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

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

หากความต้องการมีการเปลี่ยนแปลงโดยทั่วไปจะเรียกหาอัลกอริทึมที่แตกต่างกัน

ในการพัฒนาอัลกอริทึมการเปลี่ยนแปลง (การกระชับ) การทดสอบการเปรียบเทียบประสิทธิภาพเป็นล้มเหลว (สีแดง) นั้นไร้สาระ - มันไม่ได้ให้ข้อมูลเชิงลึกเกี่ยวกับการเปลี่ยนแปลงอัลกอริทึมของคุณที่จะปรับปรุงประสิทธิภาพ

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

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

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


TDD จะใช้เมื่อมีข้อกำหนดหลายประการที่สามารถเพิ่มได้เพิ่มขึ้นในชุดทดสอบของคุณ

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

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

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

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


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

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


คำสองคำแรกที่ยกมาในโพสต์ของคุณ ("ลุงบ๊อบ") หมายถึงอะไร?
Robert Harvey

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

ตกลง. แต่คุณเข้าใจความสับสนของฉันหรือไม่ ย่อหน้าแรกของคุณดูเหมือนจะยกมามีคนพูดคำว่า "ลุงบ๊อบ" ใครกำลังพูดอย่างนั้น?
Robert Harvey

@RobertHarvey ตามคำสั่ง

2

สำหรับปัญหาของคุณคุณจะต้องทำการทดสอบสองครั้ง:

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

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

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


1

ดังนั้นแทนที่จะผ่านการทดสอบมากขึ้นเช่นเดียวกับใน TDD คุณจะทำให้ "ทำงานได้ดีขึ้น"

เรียงจาก

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

หรือคุณสามารถเปรียบเทียบsort()สองชุดและตรวจสอบให้แน่ใจว่ามีการcompare()โทรตามความซับซ้อนเป้าหมายของคุณ (หรือราว ๆ นั้นถ้าคุณคาดหวังความไม่ลงรอยกัน)

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

อย่างไรก็ตาม ...

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

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

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

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


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


จุดที่ดีเยี่ยมเกี่ยวกับความเป็นไปได้ที่จะกำหนดขอบเขตของจำนวนการปฏิบัติงาน ฉันจะเถียงว่ามันเป็นพลังของ (mocks, stubs และ spies), สิ่งที่สามารถใช้ใน TDD, ซึ่งสามารถใช้ในการทดสอบประเภทอื่นได้

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

หากคุณดูที่codility.com/programmers/task/stone_wallคุณจะรู้ว่าคุณมีความซับซ้อนมากกว่า N ยกเว้นกรณีพิเศษที่คุณต้องทำงานเป็นระยะเวลานาน
Olav

@Olav "การทดสอบการเขียนจะใช้เวลานานเมื่อเทียบกับงานพิมพ์ที่ฉลาด" ... ทำอีกครั้ง ... เอ่อ .. อาจจะแต่ก็ยังเป็นที่ถกเถียงกันมาก จะทำซ้ำทุกครั้งที่สร้าง? ... ไม่อย่างแน่นอน.
svidgen

@Olav "ในโลกแห่งความเป็นจริงฉันคิดว่าคุณคงอยากรู้ว่าประสิทธิภาพลดลงในบางกรณีหรือไม่" ในบริการสดคุณต้องใช้บางอย่างเช่น New Relic เพื่อตรวจสอบประสิทธิภาพโดยรวม - ไม่ใช่วิธีการบางอย่าง และการทดสอบของคุณจะบอกคุณเมื่อโมดูลและวิธีการที่สำคัญต่อประสิทธิภาพไม่สามารถตอบสนองความคาดหวังก่อนที่คุณจะนำไปใช้
svidgen
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.