ฟังก์ชั่นแบบยาวยอมรับได้หรือไม่หากมีโครงสร้างภายใน


23

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

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


5
ฉันมักจะเขียนฟังก์ชั่น / วิธีใช้งานมากกว่า 400 บรรทัด ต้องวางคำสั่งสลับ 500 กรณีที่ไหนสักแห่ง :)
Callum Rogers

7
@Callum Rogers: ใน Python แทนที่จะมีคำสั่ง switch 500 case คุณจะต้องใช้พจนานุกรม / การค้นหาแบบแมป ฉันเชื่อว่าพวกเขาเหนือกว่าการมีคำสั่งเปลี่ยนเคส 500 หรือดังนั้น คุณยังคงต้องมีคำจำกัดความพจนานุกรม 500 บรรทัด (หรือใน Python คุณสามารถใช้การสะท้อนเพื่อระบุจำนวนแบบไดนามิก) แต่การกำหนดพจนานุกรมเป็นข้อมูลไม่ใช่รหัส และมี qualms น้อยมากเกี่ยวกับการนิยามข้อมูลขนาดใหญ่กว่านิยามฟังก์ชันขนาดใหญ่ นอกจากนี้ข้อมูลยังแข็งแกร่งกว่าโค้ด
Lie Ryan

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

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

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

คำตอบ:


21

จำไว้เสมอกฎฟังก์ชั่นทำสิ่งหนึ่งและทำได้ดี! หากคุณสามารถทำได้หลีกเลี่ยงฟังก์ชั่นที่ซ้อนกัน

มันขัดขวางการอ่านและการทดสอบ


9
ฉันเกลียดกฎนี้เสมอเพราะฟังก์ชั่นที่ทำ "สิ่งเดียว" เมื่อดูในระดับสูงของนามธรรมอาจทำ "หลายสิ่ง" เมื่อดูที่ระดับต่ำกว่า หากใช้ไม่ถูกต้องกฎ "สิ่งเดียว" อาจนำไปสู่ ​​API ที่ละเอียดเกินไป
dsimcha

1
@dsimcha: กฎอะไรที่ไม่สามารถนำไปใช้ผิด ๆ และนำไปสู่ปัญหาได้? เมื่อใครบางคนกำลังใช้ "กฎ" / ซ้ำซากผ่านจุดที่พวกเขามีประโยชน์เพียงแค่นำออกมาอีก: ภาพรวมทั้งหมดเป็นเท็จ

2
@Roger: การร้องเรียนที่ถูกต้องตามกฎหมายยกเว้นว่า IMHO มักใช้ผิดกฎในทางปฏิบัติเพื่อพิสูจน์ว่า API ที่มีการทำเกินขอบเขตและมีการปรับใช้มากเกินไป สิ่งนี้มีค่าใช้จ่ายที่เป็นรูปธรรมซึ่ง API จะยากขึ้นและใช้อย่างละเอียดมากขึ้นสำหรับกรณีการใช้งานทั่วไปที่เรียบง่าย
dsimcha

2
"ใช่กฎนั้นใช้ผิด แต่กฎนั้นใช้ผิดไปมากเกินไป!"? : P

1
ฟังก์ชั่นของฉันทำสิ่งหนึ่งและทำสิ่งนั้นได้ดีมากนั่นคือครอบครอง 27 หน้าจอ :)
Kaz

12

บางคนแย้งว่าฟังก์ชั่นสั้น ๆ ได้มากขึ้นผิดพลาดได้ง่ายกว่าฟังก์ชั่นยาว

Card and Glass (1990) ชี้ให้เห็นว่าความซับซ้อนในการออกแบบนั้นเกี่ยวข้องกับสองด้าน: ความซับซ้อนภายในแต่ละองค์ประกอบและความซับซ้อนของความสัมพันธ์ระหว่างส่วนประกอบ

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

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


คุณอ่าน "Clean Code" แล้วหรือยัง มันพูดถึงเรื่องนี้ในระยะเวลา amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/…
Martin Wickman

9

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

ฉันรู้ว่าทันทีที่ฉันกด Page Up / Down หรือย้ายไปยังส่วนอื่นของรหัสฉันจำได้เพียง 7 +/- 2 สิ่งจากหน้าก่อนหน้า และน่าเสียดายที่สถานที่เหล่านี้บางแห่งจะถูกใช้เมื่ออ่านรหัสใหม่

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


3
คุณเพียงแค่ต้องพิมพ์มันออกมาบนเครื่องพิมพ์เมทริกซ์ - ดูรหัส 10 เมตรพร้อมกัน :)

หรือรับจอภาพที่มีความละเอียดสูงกว่า
frogstarr78

และใช้งานในแนวตั้ง
Calmarius

4

เหตุใดจึงใช้ฟังก์ชั่นซ้อนกันมากกว่าฟังก์ชั่นภายนอกปกติ

แม้ว่าฟังก์ชั่นภายนอกจะเคยใช้ในฟังก์ชั่นเดียวที่เคยมีมาก่อน แต่ก็ยังทำให้อ่านง่ายขึ้น:

DoThing(int x){
    x += ;1
    int y = FirstThing(x);
    x = SecondThing(x, y);
    while(spoo > fleem){
        x = ThirdThing(x+y);
    }
}

FirstThing(int x){...}
SecondThing(int x, int y){...}
ThirdThing(int z){...}

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

แต่ฟังก์ชั่นที่ซ้อนกันจะมีประสิทธิภาพน้อยลงเพราะพวกมันต้องการการรองรับฟังก์ชั่นลงและอาจปิดศัพท์ศัพท์เต็มเป่า หากภาษานั้นได้รับการปรับปรุงอย่างไม่เหมาะสมอาจมีการทำงานเพิ่มเติมเพื่อสร้างสภาพแวดล้อมแบบไดนามิกเพื่อส่งผ่านไปยังฟังก์ชันลูก
Kaz

3

ฉันไม่ได้มีหนังสืออยู่ข้างหน้าฉันในขณะนี้ (อ้างถึง) แต่ตาม Code Complete "sweetspot" สำหรับความยาวฟังก์ชั่นอยู่ที่ประมาณ 25-50 บรรทัดของรหัสตามการวิจัยของเขา

มีหลายครั้งที่โอเคที่จะมีฟังก์ชั่นแบบยาว:

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

เวลาที่มันไม่โอเคที่จะมีฟังก์ชั่นที่ยาวนาน:

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

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

ฟังก์ชั่นขนาดเล็กมีข้อดีดังต่อไปนี้:

  • ความสามารถในการพกพา: มันง่ายกว่ามากในการย้ายฟังก์ชั่นไปรอบ ๆ
  • การดีบัก: เมื่อคุณดูสแต็คเทรซมันจะเร็วกว่ามากในการระบุข้อผิดพลาดหากคุณกำลังมองหาฟังก์ชั่นที่มีรหัส 25 บรรทัดแทนที่จะเป็น 100
  • ความสามารถในการอ่าน - ชื่อของฟังก์ชั่นบอกสิ่งที่บล็อกทั้งหมดของรหัสทำ นักพัฒนาในทีมของคุณอาจไม่ต้องการอ่านบล็อกนั้นหากพวกเขาไม่ได้ทำงานด้วย นอกจากนี้ใน IDE ที่ทันสมัยที่สุดของ dev อีกตัวหนึ่งสามารถเข้าใจได้ดีขึ้นว่าคลาสของคุณกำลังทำอะไรโดยการอ่านชื่อฟังก์ชั่นในเบราว์เซอร์วัตถุ
  • การนำทาง - IDE ส่วนใหญ่จะให้คุณค้นหาชื่อของฟังก์ชั่น นอกจากนี้ IDE ที่ทันสมัยส่วนใหญ่มีความสามารถในการดูแหล่งที่มาของฟังก์ชั่นในหน้าต่างอื่นสิ่งนี้จะช่วยให้ผู้พัฒนาระบบคนอื่น ๆ ดูฟังก์ชั่นที่ยาวนานของคุณบนหน้าจอ 2 หน้าจอ (หากจอภาพหลายจอ)

รายการไปที่ .....


2

คำตอบขึ้นอยู่กับว่าอย่างไรก็ตามคุณควรเปลี่ยนให้เป็นคลาส


ยกเว้นกรณีที่คุณไม่ได้เขียนใน OOPL ซึ่งในกรณีนี้อาจจะไม่ได้ :)
แฟรงก์ Shearar

dsimcha พูดถึงว่าพวกเขากำลังใช้ D และ Python
frogstarr78

ชั้นเรียนมักจะคับแคบเกินไปสำหรับผู้ที่ไม่เคยได้ยินสิ่งต่าง ๆ เช่นการปิดคำศัพท์
Kaz

2

ฉันไม่ชอบฟังก์ชั่นที่ซ้อนกันส่วนใหญ่ แลมบ์ดาอยู่ในหมวดหมู่นั้น แต่มักจะไม่ติดธงให้ฉันเว้นแต่ว่าพวกเขาจะมีอักขระมากกว่า 30-40 ตัว

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

ฉันคิดว่าฟังก์ชั่นควรทำสิ่งนั้น การทำสิ่งอื่น ๆ เป็นสิ่งที่ฟังก์ชั่นอื่นทำ ดังนั้นถ้าคุณมีฟังก์ชั่น 200-line ทำสิ่งนั้นและมันทั้งหมดไหลนั่นคือ A-OK


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

1

เป็นที่ยอมรับหรือไม่? นั่นเป็นคำถามที่คุณสามารถตอบได้ ฟังก์ชั่นนี้ใช้งานได้ตามที่ต้องการหรือไม่? มันสามารถบำรุงรักษาได้หรือไม่? สมาชิกในทีมของคุณยอมรับได้หรือไม่? ถ้าเป็นเช่นนั้นนั่นคือสิ่งที่สำคัญจริงๆ

แก้ไข: ฉันไม่เห็นสิ่งที่เกี่ยวกับฟังก์ชั่นที่ซ้อนกัน โดยส่วนตัวฉันจะไม่ใช้มัน ฉันจะใช้ฟังก์ชั่นปกติแทน


1

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

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

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

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


1

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


0

เมื่อฉันเขียนโปรแกรมในหลามฉันชอบที่จะถอยกลับหลังจากที่ฉันเขียนฟังก์ชั่นและถามตัวเองว่ามันเป็นไปตาม "Zen of Python" หรือไม่ (พิมพ์ 'import this' ในล่ามไพ ธ อนของคุณ):

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


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

0

วางไว้ในโมดูลแยกต่างหาก

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

  1. วางไว้ในโมดูลที่มีฟังก์ชั่น "สาธารณะ" เพียงหนึ่งเดียว
  2. ใส่ไว้ในชั้นเรียนที่มีเพียงฟังก์ชั่น "สาธารณะ" (คงที่)
  3. วางไว้ในฟังก์ชัน (ตามที่คุณอธิบาย)

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

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