แนวคิดเรื่อง“ การกลับมาครั้งเดียวเท่านั้น” มาจากไหน


1055

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

if (condition)
   return 42;
else
   return 97;

" นี่น่าเกลียดคุณต้องใช้ตัวแปรเฉพาะที่! "

int result;
if (condition)
   result = 42;
else
   result = 97;
return result;

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

แน่นอนโดยปกติฉันจะเขียน:

return (condition) ? 42 : 97;

แต่โปรแกรมเมอร์จำนวนมากหลีกเลี่ยงผู้ปฏิบัติงานที่มีเงื่อนไขและชอบแบบยาว

แนวคิดเรื่อง "หนึ่งคืนเท่านั้น" มาจากไหน มีเหตุผลทางประวัติศาสตร์ว่าทำไมการประชุมครั้งนี้เกิดขึ้น?


2
สิ่งนี้ค่อนข้างเชื่อมต่อกับการสร้างใหม่ของ Guard Clause stackoverflow.com/a/8493256/679340 Guard Clause จะเพิ่มผลตอบแทนให้กับจุดเริ่มต้นของวิธีการของคุณ และมันทำให้รหัสสะอาดขึ้นมากในความคิดของฉัน
Piotr Perak

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

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

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

3
คุณควรลบเงื่อนไขทั้งหมด คำตอบคือ 42
cambunctious

คำตอบ:


1119

"Single Entry, Single Exit" ถูกเขียนขึ้นเมื่อการเขียนโปรแกรมส่วนใหญ่ทำในภาษาแอสเซมบลี, FORTRAN หรือ COBOL มันถูกตีความผิดกันอย่างกว้างขวางเพราะภาษาที่ทันสมัยไม่สนับสนุนการปฏิบัติ Dijkstra ถูกเตือน

"Single Entry" หมายถึง "อย่าสร้างจุดเข้าอื่นสำหรับฟังก์ชั่น" ในภาษาแอสเซมบลีแน่นอนว่าเป็นไปได้ที่จะเข้าสู่ฟังก์ชั่นที่คำสั่ง FORTRAN รองรับหลายฟังก์ชั่นด้วยENTRYคำสั่ง:

      SUBROUTINE S(X, Y)
      R = SQRT(X*X + Y*Y)
C ALTERNATE ENTRY USED WHEN R IS ALREADY KNOWN
      ENTRY S2(R)
      ...
      RETURN
      END

C USAGE
      CALL S(3,4)
C ALTERNATE USAGE
      CALL S2(5)

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

C SUBROUTINE WITH ALTERNATE RETURN.  THE '*' IS A PLACE HOLDER FOR THE ERROR RETURN
      SUBROUTINE QSOLVE(A, B, C, X1, X2, *)
      DISCR = B*B - 4*A*C
C NO SOLUTIONS, RETURN TO ERROR HANDLING LOCATION
      IF DISCR .LT. 0 RETURN 1
      SD = SQRT(DISCR)
      DENOM = 2*A
      X1 = (-B + SD) / DENOM
      X2 = (-B - SD) / DENOM
      RETURN
      END

C USE OF ALTERNATE RETURN
      CALL QSOLVE(1, 0, 1, X1, X2, *99)
C SOLUTION FOUND
      ...
C QSOLVE RETURNS HERE IF NO SOLUTIONS
99    PRINT 'NO SOLUTIONS'

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


38
และอย่าลืมรหัสปาเก็ตตี้ ไม่ทราบว่ารูทีนย่อยจะออกจากการใช้ GOTO แทนการส่งคืนโดยปล่อยพารามิเตอร์การเรียกฟังก์ชันและที่อยู่ผู้ส่งคืนไว้บนสแต็ก Single exit ได้รับการส่งเสริมเป็นวิธีการอย่างน้อยช่องทางเส้นทางรหัสทั้งหมดไปยังคำสั่ง RETURN
TMN

2
@TMN: ในช่วงแรก ๆ เครื่องส่วนใหญ่ไม่มีฮาร์ดแวร์สแต็ค โดยทั่วไปการเรียกซ้ำไม่ได้รับการสนับสนุน อาร์กิวเมนต์รูทีนย่อยและแอดเดรสส่งคืนถูกเก็บในตำแหน่งคงที่ที่อยู่ติดกับโค้ดรูทีนย่อย การกลับมาเป็นแค่การอ้อมไป
วินไคลน์

5
@kevin: ใช่ แต่ตามที่คุณไม่ได้หมายถึงอีกต่อไปสิ่งที่มันถูกคิดค้นขึ้นมา (BTW ฉันจริง ๆ แน่ใจว่าเฟร็ดถามคือการตั้งค่าสำหรับการตีความปัจจุบันของ "ทางออกเดียว" มาจาก) นอกจากนี้ C มีconstมาตั้งแต่ก่อนที่ผู้ใช้จำนวนมากที่นี่เกิดมาจึงไม่จำเป็นสำหรับค่าคงที่ทุนอีกต่อไป แม้ในซี แต่Java เก็บรักษาไว้ทุกคนนิสัยไม่ดีเก่า
sbi

3
ข้อยกเว้นละเมิดการตีความ Single Exit นี้หรือไม่ (หรือลูกพี่ลูกน้องดั้งเดิมมากขึ้นของพวกเขาsetjmp/longjmp?)
Mason Wheeler

2
แม้ว่า op ถามเกี่ยวกับการตีความปัจจุบันของการกลับมาครั้งเดียวคำตอบนี้เป็นคำที่มีรากฐานทางประวัติศาสตร์มากที่สุด ไม่มีจุดในการใช้การส่งคืนเป็นกฎเว้นแต่ว่าคุณต้องการให้ภาษาของคุณตรงกับความสุดยอดของ VB (ไม่ใช่. NET) เพียงจำไว้ว่าต้องใช้ตรรกะบูลีนแบบไม่ลัดวงจรเช่นกัน
acelent

912

แนวคิดของSingle Entry, Single Exit (SESE) มาจากภาษาที่มีการจัดการทรัพยากรอย่างชัดเจนเช่น C และแอสเซมบลี ใน C รหัสเช่นนี้จะทำให้ทรัพยากรรั่วไหล:

void f()
{
  resource res = acquire_resource();  // think malloc()
  if( f1(res) )
    return; // leaks res
  f2(res);
  release_resource(res);  // think free()
}

ในภาษาดังกล่าวโดยทั่วไปคุณมีสามตัวเลือก:

  • ทำซ้ำรหัสการล้างข้อมูล
    ฮึ. ความซ้ำซ้อนนั้นไม่ดีอยู่เสมอ

  • ใช้ a gotoเพื่อข้ามไปยังรหัสการล้างข้อมูล
    สิ่งนี้ต้องใช้รหัสการล้างข้อมูลเป็นสิ่งสุดท้ายในฟังก์ชัน (และนี่คือเหตุผลที่บางคนโต้แย้งว่าgotoมีที่อยู่และมีแน่นอน - ในค.)

  • แนะนำตัวแปรท้องถิ่นและควบคุมการไหลของการควบคุมผ่านสิ่งนั้น
    ข้อเสียคือการควบคุมการไหลจัดการผ่านไวยากรณ์ (คิดว่าbreak, return, if, while) เป็นเรื่องง่ายที่จะปฏิบัติตามกว่าการควบคุมการไหลจัดการผ่านรัฐของตัวแปร (เพราะตัวแปรเหล่านั้นมีสถานะไม่เมื่อคุณมองไปที่ขั้นตอน)

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

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


อย่างไรก็ตามเมื่อภาษามีข้อยกเว้น (เกือบ) ฟังก์ชั่นใด ๆ ที่อาจจะออกก่อนเวลา (เกือบ) จุดใด ๆ ดังนั้นคุณจะต้องทำให้บทบัญญัติสำหรับการกลับมาก่อนกำหนดอยู่แล้ว (ฉันคิดว่าfinallyส่วนใหญ่จะใช้สำหรับที่ใน Java และusing(เมื่อดำเนินการIDisposableเป็นfinallyอย่างอื่น) ใน C #; C ++ แทนใช้RAII .) เมื่อคุณทำสิ่งนี้แล้วคุณไม่สามารถล้มเหลวในการทำความสะอาดหลังจากตัวเองเนื่องจากreturnคำสั่งก่อนดังนั้นสิ่งที่อาจ การโต้แย้งที่แข็งแกร่งที่สุดในความโปรดปรานของ SESE ได้หายไป

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

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


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

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


52
ขวา. ใน Java โค้ดการทำความสะอาดจะอยู่ในส่วนfinallyคำสั่งที่เรียกใช้งานโดยไม่คำนึงถึงช่วงต้นreturnหรือข้อยกเว้น
dan04

15
@ dan04 ใน Java 7 คุณไม่จำเป็นต้องfinallyใช้เวลามากนัก
R. Martinho Fernandes

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

21
@Karl: อันที่จริงมันเป็นข้อบกพร่องอย่างรุนแรงของภาษา GC เช่น Java ที่พวกเขาบรรเทาคุณจากการทำความสะอาดทรัพยากรหนึ่ง แต่ล้มเหลวกับคนอื่น ๆ ทั้งหมด (C ++ แก้ปัญหานี้สำหรับทรัพยากรทั้งหมดโดยใช้RAII .) แต่ฉันไม่ได้พูดถึงหน่วยความจำเพียงอย่างเดียว (ฉันแค่ใส่malloc()และfree()แสดงความคิดเห็นเป็นตัวอย่าง) ฉันกำลังพูดถึงทรัพยากรโดยทั่วไป ฉันไม่ได้หมายความว่า GC จะแก้ปัญหาเหล่านี้ได้ (ฉันพูดถึง C ++ ซึ่งไม่มี GC ออกจากกล่อง) จากสิ่งที่ฉันเข้าใจใน Java finallyใช้เพื่อแก้ไขปัญหานี้
sbi

10
@sbi: สิ่งที่สำคัญกว่าสำหรับฟังก์ชั่น (ขั้นตอนวิธีการ ฯลฯ ) มากกว่าการมีความยาวไม่เกินหน้ายาวสำหรับฟังก์ชั่นที่จะมีสัญญาที่กำหนดไว้อย่างชัดเจน ถ้ามันไม่ได้ทำอะไรที่ชัดเจนเพราะมันถูกสับเพื่อตอบสนองข้อ จำกัด ความยาวโดยพลการนั่นคือไม่ดี การเขียนโปรแกรมเกี่ยวกับการเล่นแตกต่างกันบางครั้งกองกำลังขัดแย้งกัน
Donal Fellows

81

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

  int function() {
     if (bidi) { print("return 1"); return 1; }
     for (int i = 0; i < n; i++) {
       if (vidi) { print("return 2"); return 2;}
     }
     print("return 3");
     return 3;
  }

ในอีกทางหนึ่งคุณสามารถ refactor สิ่งนี้ลงในการfunction()โทร_function()และบันทึกผลลัพธ์


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

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

63
สุจริตถ้าฉันรักษารหัสนั้นฉันควรจะมีการกำหนดอย่างสมเหตุสมผล_function()ด้วยreturns ในสถานที่ที่เหมาะสมและ wrapper ชื่อfunction()ที่จัดการการบันทึกภายนอกมากกว่ามีหนึ่งเดียวfunction()กับตรรกะที่บิดเบี้ยวเพื่อให้ผลตอบแทนทั้งหมดเข้าสู่ทางออกเดียว - จุดเพื่อให้ฉันสามารถแทรกคำสั่งเพิ่มเติมก่อนที่จะจุด
ruakh

11
ใน debuggers บางตัว (MSVS) คุณสามารถใส่เบรกพอยต์ในวงเล็บปิดสุดท้าย
Abyx

6
การพิมพ์! = การดีบัก นั่นไม่ใช่ข้อโต้แย้งเลย
Piotr Perak

53

"Single Entry, Single Exit" มีต้นกำเนิดมาจากการปฏิวัติการเขียนโปรแกรมอย่างมีโครงสร้างของต้นปี 1970 ซึ่งถูกส่งออกโดยจดหมายของ Edsger W. Dijkstra ถึงบรรณาธิการ " GOTO Statement พิจารณาว่าเป็นอันตราย " แนวคิดที่อยู่เบื้องหลังการเขียนโปรแกรมแบบมีโครงสร้างนั้นมีรายละเอียดในหนังสือคลาสสิก "การเขียนโปรแกรมแบบมีโครงสร้าง" โดย Ole Johan-Dahl, Edsger W. Dijkstra และ Charles Anthony Richard Hoare

"คำชี้แจงของ GOTO ที่พิจารณาว่าเป็นอันตราย" จำเป็นต้องอ่านแม้กระทั่งทุกวันนี้ "การเขียนโปรแกรมแบบมีโครงสร้าง" เป็นวันที่ แต่ยังคงมีคุณค่ามากและควรจะอยู่ในอันดับต้น ๆ ของรายการ "ต้องอ่าน" ของนักพัฒนาใด ๆ เหนือสิ่งอื่นใดจากเช่น Steve McConnell (ส่วนของ Dahl วางพื้นฐานของคลาสใน Simula 67 ซึ่งเป็นพื้นฐานทางเทคนิคสำหรับคลาสใน C ++ และการเขียนโปรแกรมเชิงวัตถุทั้งหมด)


6
บทความนี้เขียนขึ้นในวันก่อนหน้า C เมื่อ GOTO ถูกใช้งานอย่างหนัก พวกเขาไม่ใช่ศัตรู แต่คำตอบนี้ถูกต้องแน่นอน คำสั่ง return ที่ไม่ได้อยู่ท้ายฟังก์ชั่นนั้นเป็น goto อย่างมีประสิทธิภาพ
user606723

31
บทความนี้ยังถูกเขียนขึ้นในสมัยเมื่อgotoแท้จริงสามารถไปได้ทุกที่เช่นขวาเข้าไปในจุดสุ่มบางอย่างในฟังก์ชั่นอื่นผ่านความคิดใด ๆ ของขั้นตอนการทำงานที่เรียกกอง ฯลฯ gotoไม่มีภาษาสติอนุญาตว่าวันนี้มีตรง C's setjmp/ longjmpเป็นกรณีพิเศษกึ่งเดียวที่ฉันรับรู้และถึงแม้จะต้องใช้ความร่วมมือจากทั้งสองด้าน (กึ่งประชดที่ฉันใช้คำว่า "พิเศษ" ที่นั่นแม้ว่าการพิจารณาว่าข้อยกเว้นทำในสิ่งเดียวกัน ... ) โดยทั่วไปบทความสนับสนุนการฝึกฝนที่ตายไปนานแล้ว
cHao

5
จากย่อหน้าสุดท้ายของ "คำแถลง Goto ถือว่าเป็นอันตราย": "ใน [2] Guiseppe Jacopini ดูเหมือนว่าจะพิสูจน์ความไม่จำเป็น (ตรรกะ) ของคำแถลงการออกกำลังกายเพื่อแปลแผนภาพการไหลโดยพลการมากหรือน้อยลงในกลไก - น้อยหนึ่งแต่ไม่ได้ที่จะได้รับการแนะนำ . แล้วแผนภาพการไหลส่งผลให้ไม่สามารถคาดว่าจะมีความโปร่งใสมากขึ้นกว่าเดิมอีก. "
hugomg

10
สิ่งนี้เกี่ยวข้องกับคำถามได้อย่างไร ใช่งานของ Dijkstra นำไปสู่ภาษา SESE ในที่สุดแล้วจะเป็นอย่างไร งานของ Babbage ก็เช่นกัน และบางทีคุณควรอ่านกระดาษอีกครั้งหากคุณคิดว่ามันมีอะไรเกี่ยวกับการมีจุดออกหลายจุดในฟังก์ชั่น เพราะมันไม่
jalf

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

35

มันง่ายเสมอที่จะเชื่อมโยง Fowler

หนึ่งในตัวอย่างหลักที่ขัดแย้งกับ SESE คือคำสั่งป้องกัน:

แทนที่ Nested Conditional ด้วย Guard Clauses

ใช้ Guard Clauses สำหรับทุกกรณีพิเศษ

double getPayAmount() {
    double result;
    if (_isDead) result = deadAmount();
    else {
        if (_isSeparated) result = separatedAmount();
        else {
            if (_isRetired) result = retiredAmount();
            else result = normalPayAmount();
        };
    }
return result;
};  

                                                                                                         http://www.refactoring.com/catalog/arrow.gif

double getPayAmount() {
    if (_isDead) return deadAmount();
    if (_isSeparated) return separatedAmount();
    if (_isRetired) return retiredAmount();
    return normalPayAmount();
};  

สำหรับข้อมูลเพิ่มเติมโปรดดูที่หน้า 250 ของRefactoring ...


11
อีกตัวอย่างที่ไม่ดี: มันสามารถแก้ไขได้อย่างง่ายดายด้วย else-ifs
แจ็ค

1
ตัวอย่างของคุณไม่ยุติธรรมเท่าไหร่: double getPayAmount () {double ret = normalPayAmount (); ถ้า (_isDead) ret = deadAmount (); ถ้า (_isSeparated) ret = separAmount (); ถ้า (_isRetired) ret = เกษียณAmount (); ย้อนกลับไป; };
Charbel

6
@ Charbel นั่นไม่ใช่สิ่งเดียวกัน ถ้า_isSeparatedและ_isRetiredสามารถเป็นจริงได้ (และทำไมจึงไม่เป็นไปได้?) คุณคืนค่าผิดจำนวน
hvd

2
@Konchog " เงื่อนไขที่ซ้อนกันจะให้เวลาดำเนินการดีกว่าข้อระวัง " นี่majorlyต้องการอ้างอิง ฉันสงสัยว่ามันเป็นความจริงได้อย่างน่าเชื่อถือ ในกรณีนี้ตัวอย่างเช่นวิธีการคืนต้นแตกต่างจากตรรกะลัดวงจรในแง่ของการสร้างรหัส? แม้ว่ามันจะมีความสำคัญฉันก็ไม่สามารถจินตนาการได้ถึงกรณีที่ความแตกต่างนั้นจะยิ่งใหญ่กว่าเศษไม้เล็ก ๆ น้อย ๆ ดังนั้นคุณจึงใช้การเพิ่มประสิทธิภาพก่อนกำหนดโดยทำให้โค้ดอ่านน้อยลงเพียงเพื่อสนองประเด็นทางทฤษฎีที่ไม่ได้รับการพิสูจน์เกี่ยวกับสิ่งที่คุณคิดว่านำไปสู่โค้ดที่เร็วขึ้นเล็กน้อย เราไม่ทำอย่างนั้น
underscore_d

1
@underscore_d คุณพูดถูก มันขึ้นอยู่กับคอมไพเลอร์ แต่มันอาจใช้พื้นที่มากขึ้น .. ลองดูชุดประกอบสองตัวปลอมและมันง่ายที่จะดูว่าทำไมส่วนคำสั่งป้องกันมาจากภาษาระดับสูง การทดสอบ "A" (1); สิ้นสุด branch_fail; การทดสอบ (2); สิ้นสุด branch_fail; การทดสอบ (3) สิ้นสุด branch_fail; {CODE} end: return; การทดสอบ "B" (1); branch_good next1; กลับ; next1: ทดสอบ (2); branch_good next2; กลับ; next2: ทดสอบ (3); branch_good next3; กลับ; next3: {CODE} คืน
Konchog

11

ฉันเขียนโพสต์บล็อกในหัวข้อนี้สักครู่

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

คำถามนี้ถูกถามใน Stackoverflow ด้วย


เฮ้ฉันไม่สามารถเข้าถึงลิงค์นั้นอีกต่อไป คุณมีเวอร์ชันที่โฮสต์อยู่ที่ไหนบางแห่งที่ยังคงเข้าถึงได้หรือไม่
Nic Hartley

สวัสดี QPT เป็นจุดที่ดี ฉันนำโพสต์บล็อกกลับมาและอัปเดต URL ด้านบน มันควรจะลิงค์ตอนนี้!
แอนโธนี

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

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

เมห์ราดถ้ามีการศึกษาอย่างเป็นทางการเพื่อสนับสนุนให้แสดงมัน นั่นคือทั้งหมดที่ การยืนยันในหลักฐานต่อต้านเป็นการเปลี่ยนภาระการพิสูจน์
แอนโทนี่

7

การคืนหนึ่งทำให้การปรับโครงสร้างทำได้ง่ายขึ้น ลองทำการ "แยกเมธอด" ลงในเนื้อความด้านในของ for loop ที่มี return, break หรือ Continue สิ่งนี้จะล้มเหลวเมื่อคุณทำผิดขั้นตอนการควบคุมของคุณ

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

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


เช่นเดียวกับตัวแปร ซึ่งเป็นทางเลือกในการใช้ control-flow-constructs เช่น return ก่อน
Deduplicator

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

5

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

อย่างไรก็ตามฉันไม่พิจารณาประเภทของ GOTO ที่เป็นอันตรายเหล่านี้และจะไม่ลังเลที่จะใช้ GOTO จริงในรหัสของฉันหากฉันพบว่ามีเหตุผลที่ดี

กฎทั่วไปของฉันคือ GOTO นั้นใช้สำหรับควบคุมการไหลเท่านั้น ไม่ควรใช้การวนซ้ำใด ๆ และคุณไม่ควรใช้ GOTO 'ขึ้น' หรือ 'ย้อนกลับ' (ซึ่งเป็นวิธีการแบ่ง / ส่งคืนงาน)

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

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


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

Touche แต่นี่ไม่ได้แตกต่างจากจุดของฉัน ... มันแค่ทำให้คำอธิบายของฉันผิดเล็กน้อย โอ้ดี
user606723

GOTO ควรจะใช้ได้เสมอตราบใดที่ (1) เป้าหมายอยู่ในวิธีการหรือฟังก์ชันเดียวกันและ (2) ทิศทางอยู่ข้างหน้าในรหัส (ข้ามรหัสบางส่วน) และ (3) เป้าหมายไม่ได้อยู่ในโครงสร้างซ้อนกันอื่น (เช่น GOTO จากตรงกลางของ if-case ถึงกลางของเคสอื่น) หากคุณปฏิบัติตามกฎเหล่านี้การใช้ GOTO ในทางที่ผิดทั้งหมดจะมีรหัสที่แข็งแกร่งมากซึ่งมีทั้งกลิ่นที่มองเห็นและมีเหตุผล
Mikko Rantalainen

3

ความซับซ้อนของวัฏจักร

ฉันเคยเห็น SonarCube ใช้คำสั่งคืนหลายคำสั่งในการพิจารณาความซับซ้อนของวัฏจักร ดังนั้นข้อความสั่งกลับยิ่งมีความซับซ้อนมากขึ้น

ผลตอบแทนประเภทการเปลี่ยนแปลง

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

หลายทางออก

การ debug นั้นยากกว่าเนื่องจากจะต้องมีการศึกษาอย่างรอบคอบควบคู่ไปกับคำแถลงเงื่อนไขเพื่อทำความเข้าใจว่าอะไรเป็นสาเหตุของค่าที่ส่งคืน

โซลูชันที่ปรับโครงสร้างใหม่

วิธีการแก้ปัญหาคำสั่ง return หลายคำคือการแทนที่ด้วย polymorphism มีการคืนค่าครั้งเดียวหลังจากแก้ไขวัตถุการนำไปปฏิบัติที่ต้องการ


3
การย้ายจากการส่งคืนหลายรายการเป็นการตั้งค่าส่งคืนในหลาย ๆ ที่ไม่ได้ขจัดความซับซ้อนของวงจรออกไป ปัญหาทั้งหมดที่ความซับซ้อนของวัฏจักรสามารถบ่งชี้ได้ในบริบทที่กำหนดยังคงอยู่ "มันยากที่จะทำการดีบั๊กเนื่องจากลอจิกจะต้องมีการศึกษาอย่างรอบคอบควบคู่ไปกับคำสั่งแบบมีเงื่อนไขเพื่อทำความเข้าใจว่าอะไรทำให้เกิดค่าที่ส่งคืน" อีกครั้งตรรกะไม่เปลี่ยนแปลงโดยการรวมการส่งคืน หากคุณต้องศึกษารหัสอย่างละเอียดเพื่อที่จะเข้าใจว่ามันทำงานอย่างไรมันจำเป็นต้องได้รับการปรับโครงสร้างใหม่หยุดอย่างสมบูรณ์
WillD
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.