ฉันควรแยกฟังก์ชันการทำงานเฉพาะลงในฟังก์ชั่นและทำไม?


29

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

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

ฉันควรทำอย่างนั้นก่อนที่ฉันจะเขียนรหัสทั้งหมดหรือฉันควรปล่อยไว้จนกว่าทุกอย่างจะเสร็จสิ้นแล้วแยกฟังก์ชั่น?


19
"ฉันปล่อยมันไว้จนกระทั่งทุกอย่างเสร็จสิ้น" มักจะพ้องกับ "จะไม่มีวันทำ"
ร่าเริง

2
โดยทั่วไปแล้วเป็นความจริง แต่ยังจำหลักการตรงกันข้ามของ YAGNI (ซึ่งไม่ได้ใช้ในกรณีนี้เนื่องจากคุณต้องการมันอยู่แล้ว)
jhocking


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

คำตอบ:


35

นี่คือหนังสือที่ฉันเชื่อมโยงบ่อยครั้ง แต่ที่นี่ฉันกลับไปอีกครั้ง: รหัสสะอาดของโรเบิร์ตซี. มาร์ตินบทที่ 3 "ฟังก์ชั่น"

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

คุณชอบอ่านฟังก์ชั่นที่มี +150 บรรทัดหรือฟังก์ชั่นที่เรียก 3 +50 ฟังก์ชั่นสาย? ฉันคิดว่าฉันชอบตัวเลือกที่สอง

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

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

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

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

ฉันควรทำอย่างนั้นก่อนที่ฉันจะเขียนรหัสทั้งหมดหรือฉันควรปล่อยไว้จนกว่าทุกอย่างจะเสร็จสิ้น

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


10
ที่จริงบ๊อบมาร์ตินได้แสดงให้เห็นหลายครั้งว่าเขาชอบ 7 ฟังก์ชั่น 2 ถึง 3 สายกว่าหนึ่งฟังก์ชั่นที่มี 15 สาย (ดูที่นี่sites.google.com/site/unclebobconsultingllc/... ) และนั่นคือสิ่งที่นักพัฒนาที่มีประสบการณ์จำนวนมากกำลังต่อต้าน โดยส่วนตัวแล้วฉันคิดว่า "devs ที่มีประสบการณ์" จำนวนมากมีปัญหาในการยอมรับว่าพวกเขายังคงสามารถปรับปรุงสิ่งพื้นฐานเช่นการสร้าง abstractions ด้วยฟังก์ชันหลังจากการเข้ารหัสนานกว่า 10 ปี
Doc Brown

+1 สำหรับการอ้างอิงหนังสือที่ควรให้ความเห็นเล็ก ๆ น้อย ๆ อยู่ในชั้นวางของ บริษัท ซอฟต์แวร์ใด ๆ
Fabio Marcolini

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

คุณพูดถูก!
Jalayn

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

13

ใช่แน่นอน ถ้ามันง่ายที่จะเห็นและแยก "งาน" ที่แตกต่างกันของฟังก์ชั่นเดียว

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

แต่อาจมีปัญหากับสิ่งนี้:

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

5

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

มันตกลงไหมที่จะแบ่งฟังก์ชั่นและเมธอดที่ยาวออกเป็นอันเล็ก ๆ แม้ว่าจะไม่ได้รับการเรียกจากสิ่งอื่นใดเลย?

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

มือโปร:

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

Contra:

  • ใช้หน้าจอหลายหน้า
  • ใช้เวลาในการอ่านนาน
  • ไม่ใช่เรื่องง่ายที่จะจดจำทุกขั้นตอน

ทีนี้ลองจินตนาการถึงการแบ่งฟังก์ชั่นที่ยาวออกเป็นหลายฟังก์ชั่นย่อยและมองพวกมันด้วยมุมมองที่กว้างขึ้น

มือโปร:

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

Contra:

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

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


2

สำหรับฉันมีสี่เหตุผลในการแตกบล็อกโค้ดลงในฟังก์ชั่น:

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

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

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

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


1

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

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


0

นอกจากนี้ฉันเขียนสิ่งนี้เพื่อตอบคำถามของdallin (ตอนนี้ปิด) แต่ฉันก็ยังรู้สึกว่ามันจะมีประโยชน์กับคนที่นี่


ฉันคิดว่าเหตุผลของฟังก์ชั่น atomising คือ 2 เท่าและเนื่องจาก @jozefg กล่าวถึงนั้นขึ้นอยู่กับภาษาที่ใช้

การแยกความกังวล

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

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

พูดใน JavaScript คุณมีฟังก์ชั่น getMyData () ซึ่ง 1) สร้างข้อความสบู่จากพารามิเตอร์ 2) เริ่มต้นการอ้างอิงบริการ 3) เรียกใช้บริการที่มีข้อความสบู่ 4) แยกผล 5) ผลตอบแทน ดูเหมือนว่าสมเหตุสมผลฉันได้เขียนฟังก์ชันตรงนี้หลายครั้ง แต่จริงๆแล้วสามารถแบ่งออกเป็น 3 ฟังก์ชั่นส่วนตัวเท่านั้นรวมถึงรหัสสำหรับ 3 และ 5 (ถ้านั้น) เพราะไม่มีรหัสอื่นใดที่รับผิดชอบโดยตรงในการรับข้อมูลจากบริการ .

ปรับปรุงประสบการณ์การดีบัก

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

  • รับข้อมูลของฉัน
    • สร้างข้อความสบู่
    • การอ้างอิงบริการเบื้องต้น
    • แยกการตอบสนองบริการ - ข้อผิดพลาด

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

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

FYI - นี่คือหนึ่งในสิ่งที่ "ทำตามที่ฉันบอกไม่ใช่อย่างที่ฉันทำ" การทำให้รหัสอะตอมไม่มีจุดหมายเว้นแต่คุณจะสอดคล้องกับมันอย่างโหดเหี้ยม IMHO ซึ่งฉันไม่ชอบ

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