วิธีการสกัดเทียบกับสมมติฐาน


27

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

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

ทางออกเดียวที่ฉันเคยเห็นคือwhereclause ใน Haskell ซึ่งช่วยให้คุณกำหนดฟังก์ชันขนาดเล็กที่ใช้เฉพาะในฟังก์ชัน "parent" โดยทั่วไปดูเหมือนว่านี้:

len x y = sqrt $ (sq x) + (sq y)
    where sq a = a * a

แต่ภาษาอื่นที่ฉันใช้ไม่มีอะไรแบบนี้ - สิ่งที่ใกล้เคียงที่สุดคือการกำหนดแลมบ์ดาในขอบเขตท้องถิ่นซึ่งอาจทำให้สับสนมากขึ้น

ดังนั้นคำถามของฉันคือ - คุณพบสิ่งนี้และคุณยังเห็นว่านี่เป็นปัญหาหรือไม่ หากคุณทำเช่นนั้นคุณจะแก้ปัญหาอย่างไรโดยเฉพาะในภาษา OOP "ที่สำคัญ" เช่น Java / C # / C ++

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

อัปเดต:หากคุณทำตามคำถามและการสนทนาที่อยู่ด้านล่างคุณอาจสนุกกับบทความนี้โดย John Carmackโดยเฉพาะ:

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




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

2
@gnat มีคำถามที่เกี่ยวข้องอื่น ๆ เชื่อมโยงจากที่นั่น แต่ไม่มีสิ่งใดที่กล่าวถึงความจริงที่ว่ารหัสนี้อาจอาศัยสมมติฐานที่เฉพาะเจาะจงซึ่งมีผลเฉพาะในบริบทของผู้โทรเท่านั้น
Max Yankov

1
@Doval ในประสบการณ์ของฉันมันทำจริงๆ เมื่อมีวิธีการของผู้ช่วยเหลือที่ลำบากอยู่รอบ ๆ เหมือนที่คุณอธิบายการแยกชั้นเหนียวใหม่จะจัดการเรื่องนี้
gnat

คำตอบ:


29

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

ความกังวลของคุณได้รับการยอมรับอย่างดี มีวิธีแก้ไขปัญหาอื่น

ย้อนกลับไป จุดประสงค์ของวิธีการพื้นฐานคืออะไร? วิธีการทำอย่างใดอย่างหนึ่งในสองสิ่ง:

  • สร้างมูลค่า
  • ทำให้เกิดผลกระทบ

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

คุณทราบว่าวิธีการที่เรียกว่าใน "บริบท" บริบทนั้นคืออะไร

  • ค่าของข้อโต้แย้ง
  • สถานะของโปรแกรมภายนอกเมธอด

สิ่งสำคัญที่คุณกำลังชี้ให้เห็นคือความถูกต้องของผลลัพธ์ของวิธีการนั้นขึ้นอยู่กับบริบทที่เรียกใช้

ที่เราเรียกว่าเงื่อนไขที่จำเป็นก่อนที่ร่างกายวิธีการเริ่มต้นสำหรับวิธีการในการผลิตผลที่ถูกต้องของปัจจัยพื้นฐานและที่เราเรียกว่าเงื่อนไขที่จะมีการผลิตหลังจากที่ร่างกายวิธีการส่งกลับของpostconditions

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

วิธีการแก้ปัญหานี้คือทำให้ปัจจัยพื้นฐานและ postconditions อย่างชัดเจนในโปรแกรม ตัวอย่างเช่นใน C # คุณสามารถใช้Debug.Assertหรือ Code Contracts เพื่อแสดงเงื่อนไขเบื้องต้นและ postconditions

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

แน่นอนว่ามีหลายวิธีที่การออกแบบวิธีการที่ดีจะช่วยลดปัญหาที่คุณพบ:

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

+1 สำหรับการเป็นคำตอบที่อธิบายถึงปัญหาในแง่ของเงื่อนไข / postconditions
QuestionC

5
ฉันจะเพิ่มว่ามันมักจะเป็นไปได้ (และความคิดที่ดี!) เพื่อมอบหมายการตรวจสอบก่อนและหลังเงื่อนไขเพื่อระบบประเภท หากคุณมีฟังก์ชันที่ใช้stringและบันทึกลงในฐานข้อมูลแสดงว่าคุณมีความเสี่ยงในการฉีด SQL ถ้าคุณลืมทำความสะอาด หากในทางกลับกันฟังก์ชั่นของคุณใช้เวลาSanitisedStringและวิธีเดียวที่จะได้รับSantisiedStringคือโดยการโทรSanitiseจากนั้นคุณได้จัดการข้อบกพร่องการฉีด SQL โดยการก่อสร้าง ฉันพบว่าตัวเองกำลังมองหาวิธีที่จะทำให้คอมไพเลอร์ปฏิเสธรหัสที่ไม่ถูกต้อง
Benjamin Hodgson

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

"บริบทนั้นคืออะไร" เพื่อชี้แจงฉันส่วนใหญ่หมายถึงสถานะส่วนตัวของวัตถุที่เรียกวิธีนี้ ฉันเดาว่ามันรวมอยู่ในหมวดหมู่ที่สอง
Max Yankov

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

13

ฉันมักจะเห็นสิ่งนี้และยอมรับว่ามันเป็นปัญหา ฉันมักจะแก้ไขได้โดยการสร้างวัตถุวิธี : ชั้นพิเศษใหม่ที่มีสมาชิกเป็นตัวแปรท้องถิ่นจากวิธีเดิมที่มีขนาดใหญ่เกินไป

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


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

เมื่อเร็ว ๆ นี้ฉันเพิ่งอยู่ในสถานการณ์ที่เหมาะสำหรับสิ่งนี้จากมุมมองสถาปัตยกรรม ฉันเขียนซอฟต์แวร์ตัวแสดงภาพพร้อมคลาสตัวแสดงผลและวิธีการเรนเดอร์แบบสาธารณะซึ่งมีบริบทจำนวนมากที่ใช้เรียกวิธีอื่น ฉันไตร่ตรองการสร้างคลาส RenderContext แยกจากกันสำหรับเรื่องนี้อย่างไรก็ตามดูเหมือนว่าจะสิ้นเปลืองอย่างมากในการจัดสรรและยกเลิกการจัดสรรโครงการนี้ทุกเฟรม github.com/golergka/tinyrenderer/blob/master/src/renderer.h
Max Yankov

6

หลายภาษาให้คุณซ้อนฟังก์ชันต่างๆเช่น Haskell Java / C # / C ++ เป็นค่าผิดปกติที่สัมพันธ์กันในเรื่องนั้น แต่น่าเสียดายที่พวกเขาเป็นที่นิยมเพื่อให้คนเข้ามาคิดว่า "มันมีจะเป็นความคิดที่ไม่ดีมิฉะนั้นชื่นชอบภาษา 'หลัก' ของฉันจะช่วยให้มัน."

Java / C # / C ++ โดยพื้นฐานแล้วคิดว่าคลาสควรเป็นวิธีการจัดกลุ่มเดียวที่คุณต้องการ หากคุณมีวิธีการมากมายที่คุณไม่สามารถกำหนดบริบทได้มีวิธีการทั่วไปสองวิธีที่ใช้: เรียงลำดับตามบริบทหรือแยกตามบริบท

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

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


ฟังก์ชั่นการสร้างรัง ... นั่นไม่ใช่ฟังก์ชั่นแลมบ์ดาใน C # (และ Java 8)
Arturo Torres Sánchez

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

ตัวอย่างไพ ธ อนนั้นเป็นไปได้อย่างแน่นอนใน C # ยกตัวอย่างเช่นปัจจัย พวกเขาอาจจะ verbose มากขึ้น แต่เป็นไปได้ 100%
Arturo Torres Sánchez

2
ไม่มีใครพูดว่าเป็นไปไม่ได้ OP ยังกล่าวถึงการใช้ lambdas ในคำถามของเขา เป็นเพียงว่าถ้าคุณแยกวิธีการเพื่อให้สามารถอ่านได้มันจะดีถ้ามันอ่านได้มากกว่า
Karl Bielefeldt

ย่อหน้าแรกของคุณดูเหมือนจะบ่งบอกว่าเป็นไปไม่ได้โดยเฉพาะอย่างยิ่งกับข้อความของคุณ: "ต้องเป็นความคิดที่ดีมิฉะนั้นภาษา 'หลัก' ที่ฉันโปรดปรานจะอนุญาต
Arturo Torres Sánchez

4

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

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

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

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


1

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

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

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

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

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

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

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

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


0

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

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

คุณได้พูดถึงวิธีแก้ปัญหาอื่นแล้ว - กำหนดตัวช่วยภายในฟังก์ชั่นหลัก มันอาจเป็นสำนวนที่ค่อนข้างแปลกในบางภาษา แต่ฉันไม่คิดว่ามันจะสับสน (เว้นแต่เพื่อนของคุณจะสับสนโดย lambdas โดยทั่วไป) ใช้งานได้เฉพาะในกรณีที่คุณสามารถกำหนดฟังก์ชั่นหรือวัตถุที่คล้ายฟังก์ชั่นได้อย่างง่ายดาย ยกตัวอย่างเช่นฉันจะไม่ลองสิ่งนี้ใน Java 7 เนื่องจากคลาสที่ไม่ระบุชื่อต้องการการแนะนำการซ้อน 2 ระดับสำหรับแม้แต่ฟังก์ชั่นที่เล็กที่สุด นี่ใกล้เคียงกับ a letหรือwhereclause เท่าที่คุณจะได้; คุณสามารถอ้างถึงตัวแปรโลคัลก่อนที่นิยามและตัวช่วยจะไม่สามารถใช้ได้นอกขอบเขตนั้น

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