การใช้วิธีกลับภายในโมฆะเป็นวิธีปฏิบัติที่ไม่ดีหรือไม่?


92

ลองนึกภาพรหัสต่อไปนี้:

void DoThis()
{
    if (!isValid) return;

    DoThat();
}

void DoThat() {
    Console.WriteLine("DoThat()");
}

สามารถใช้ return ภายใน void method ได้หรือไม่? มันมีโทษประสิทธิภาพหรือไม่? หรือจะดีกว่าถ้าเขียนโค้ดแบบนี้:

void DoThis()
{
    if (isValid)
    {
        DoThat();
    }
}
c#  return  void 

1
สิ่งที่เกี่ยวกับ: โมฆะ DoThis () {if (isValid) DoThat (); }
Dscoduc

30
นึกรหัส? ทำไม? อยู่ตรงนั้น! :-D
STW

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

คำตอบ:


173

ผลตอบแทนในวิธีการที่เป็นโมฆะไม่ได้เลวร้ายเป็นเรื่องธรรมดาที่จะคว่ำifงบเพื่อลดการทำรัง

และการมีวิธีการซ้อนกันน้อยลงช่วยเพิ่มความสามารถในการอ่านโค้ดและการบำรุงรักษา

จริงๆแล้วถ้าคุณมีเมธอด void โดยไม่มีคำสั่ง return คอมไพลเลอร์จะสร้างคำสั่ง retที่ท้ายคำสั่งเสมอ


33

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

พิจารณา:

void MyFunc(object obj)
{
    if (obj != null)
    {
        obj.DoSomething();
    }
}

เทียบกับ:

void MyFunc(object obj)
{
    if (obj == null)
        return;

    obj.DoSomething();
}

ลองนึกภาพโปรแกรมเมอร์คนอื่นเพิ่มบรรทัด: obj.DoSomethingElse ();

void MyFunc(object obj)
{
    if (obj != null)
    {
        obj.DoSomething();
    }

    obj.DoSomethingElse();
}

void MyFunc(object obj)
{
    if (obj == null)
        return;

    obj.DoSomething();
    obj.DoSomethingElse();
}

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

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


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

18
ฉันเถียงในความโปรดปรานของการลดการทำรัง! :-)
Jason Williams

ฉันเห็นด้วยกับเรื่องนี้ นอกจากนี้จากจุดยืนของ refactor จะง่ายและปลอดภัยกว่าในการ refactor วิธีการถ้า obj กลายเป็นโครงสร้างหรือสิ่งที่คุณสามารถรับประกันได้ว่าจะไม่เป็นโมฆะ
Phil Cooper

18

ปฏิบัติไม่ดี ??? ไม่มีทาง. ในความเป็นจริงการจัดการการตรวจสอบความถูกต้องจะดีกว่าเสมอโดยการส่งคืนจากวิธีการอย่างเร็วที่สุดหากการตรวจสอบล้มเหลว มิฉะนั้นจะส่งผลให้ ifs & elses ซ้อนกันจำนวนมาก การยุติก่อนกำหนดช่วยเพิ่มความสามารถในการอ่านโค้ด

ตรวจสอบคำตอบในคำถามที่คล้ายกัน: ฉันควรใช้คำสั่ง return / continue แทน if-else หรือไม่?


8

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


8

ตัวอย่างแรกคือการใช้คำสั่งยาม จากWikipedia :

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

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

โดยทั่วไปแล้วจะต้องการสิ่งนี้:

void DoThis()
{
  if (guard1) return;
  if (guard2) return;
  ...
  if (guardN) return;

  DoThat();
}

ฉันคิดว่ามันสามารถอ่านได้มากกว่านี้:

void DoThis()
{
  if (guard1 && guard2 && guard3)
  {
    DoThat();
  }
}

3

ไม่มีการลงโทษด้านประสิทธิภาพอย่างไรก็ตามส่วนที่สองของโค้ดสามารถอ่านได้มากกว่าและดูแลรักษาง่ายกว่า


รัสเซลฉันไม่เห็นด้วยกับความคิดเห็นของคุณ แต่คุณไม่ควรได้รับการโหวตลดลง +1 เพื่อให้มันออกมา Btw ฉันเชื่อว่าการทดสอบบูลีนและส่งกลับในบรรทัดเดียวตามด้วยบรรทัดว่างเป็นตัวบ่งชี้ที่ชัดเจนว่าเกิดอะไรขึ้น เช่นตัวอย่างแรกของ Rodrigo
Paul Sasik

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

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

2

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


0

ไม่เป็นไรและไม่มี 'โทษประสิทธิภาพ' แต่ไม่เคยเขียนคำสั่ง 'if' โดยไม่มีวงเล็บ

เสมอ

if( foo ){
    return;
}

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


2
อ่านได้เป็นอัตนัย imho อะไรก็ตามที่เพิ่มเข้าไปในโค้ดที่ไม่จำเป็นทำให้อ่านได้น้อยลง ... (ฉันต้องอ่านเพิ่มเติมแล้วฉันก็สงสัยว่าทำไมมันถึงอยู่ที่นั่นและเสียเวลาพยายามทำให้แน่ใจว่าฉันไม่ได้พลาดอะไรบางอย่าง) ... แต่นั่นเป็นของฉัน ความคิดเห็นส่วนตัว
Charles Bretana

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

2
Silky {กรุณากดก่อนของคุณ เส้นนี้ของคุณ{กับคุณ}ในคอลัมน์เดียวกันซึ่งอย่างมากช่วยให้สามารถอ่านได้ (ง่ายมากที่จะหาสิ่งที่สอดคล้องกันเปิดวงเล็บ / ปิด)
Imagist

1
@Imagist ฉันจะปล่อยให้มันเป็นความชอบส่วนบุคคล; และมันก็ทำได้ในแบบที่ฉันชอบ :)
เที่ยงไหม

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

0

ฉันจะไม่เห็นด้วยกับ whippersnappers รุ่นเยาว์ของคุณทั้งหมด

การใช้ผลตอบแทนกลางวิธีถือเป็นโมฆะหรืออย่างอื่นเป็นการปฏิบัติที่ไม่ดีด้วยเหตุผลที่มีการพูดชัดแจ้งอย่างชัดเจนเมื่อเกือบสี่สิบปีที่แล้วโดย Edsger W. "และดำเนินการต่อใน" Structured Programming "โดย Dahl, Dijkstra และ Hoare

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

"แถลงการณ์ของ GOTO ถือว่าเป็นอันตราย" และ "การเขียนโปรแกรมเชิงโครงสร้าง" เริ่มต้นการปฏิวัติ "การเขียนโปรแกรมเชิงโครงสร้าง" ในปี 1970 ทั้งสองส่วนนี้เป็นเหตุผลที่เรามี if-then-else, while-do และโครงสร้างการควบคุมที่ชัดเจนอื่น ๆ ในปัจจุบันและเหตุใดคำสั่ง GOTO ในภาษาระดับสูงจึงอยู่ในรายการสัตว์ใกล้สูญพันธุ์ (ความเห็นส่วนตัวของฉันคือพวกมันต้องอยู่ในรายชื่อพันธุ์ที่สูญพันธุ์)

เป็นที่น่าสังเกตว่า Message Flow Modulator ซึ่งเป็นซอฟต์แวร์ทางทหารชิ้นแรกที่เคยผ่านการทดสอบการยอมรับในการลองครั้งแรกโดยไม่มีการเบี่ยงเบนการสละสิทธิ์หรือ "ใช่ แต่" ใช้คำฟุ่มเฟือยเขียนด้วยภาษาที่ไม่มีแม้แต่ คำสั่ง GOTO

นอกจากนี้ยังควรค่าแก่การกล่าวถึงว่า Nicklaus Wirth เปลี่ยนความหมายของคำสั่ง RETURN ใน Oberon-07 ซึ่งเป็นเวอร์ชันล่าสุดของภาษาการเขียนโปรแกรม Oberon ทำให้เป็นส่วนต่อท้ายของการประกาศขั้นตอนการพิมพ์ (เช่นฟังก์ชัน) แทนที่จะเป็น คำสั่งปฏิบัติการในเนื้อหาของฟังก์ชัน ชี้แจงของเขาจากการเปลี่ยนแปลงกล่าวว่าเขาไม่ได้อย่างแม่นยำเพราะรูปแบบก่อนหน้านี้WASเป็นการละเมิดหลักการหนึ่งทางออกของการเขียนโปรแกรมโครงสร้าง


2
@ จอห์น: เราได้ข้ามคำสั่งห้ามของ Dykstra เกี่ยวกับการส่งคืนหลายครั้งในช่วงเวลาที่เราได้รับมากกว่า Pascal (พวกเราส่วนใหญ่อยู่แล้ว)
John Saunders

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

@nairdaen: ยังคงมีข้อถกเถียงเกี่ยวกับข้อยกเว้นในไตรมาสนั้น แนวทางของฉันมีดังนี้: หากระบบที่อยู่ระหว่างการพัฒนาต้องแก้ไขปัญหาที่ทำให้เกิดสภาพพิเศษดั้งเดิมและฉันไม่รังเกียจที่จะโกรธคนที่จะต้องเขียนโค้ดนั้นฉันจะโยนข้อยกเว้น จากนั้นฉันก็ถูกตะโกนในที่ประชุมเพราะผู้ชายคนนั้นไม่สนใจที่จะจับข้อยกเว้นและแอปก็ล้มเหลวในการทดสอบและฉันอธิบายว่าทำไมเขาต้องแก้ไขปัญหาและสิ่งต่างๆก็สงบลงอีกครั้ง
John R.Strohm

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

@ Jason: คำถามเดิมไม่ได้เจาะจงเกี่ยวกับคำสั่งยาม แต่เกี่ยวกับคำสั่งส่งคืนแบบสุ่มที่อยู่ตรงกลางของวิธีการ ตัวอย่างที่ยกมาดูเหมือนจะเป็นยาม ปัญหาสำคัญคือที่ไซต์ส่งคืนคุณต้องการให้เหตุผลเกี่ยวกับสิ่งที่วิธีนี้ทำหรือไม่ทำและการส่งคืนแบบสุ่มทำให้ยากขึ้นด้วยเหตุผลเดียวกันกับที่ GOTO แบบสุ่มทำให้ยากขึ้น ดู: Dijkstra, "คำชี้แจงของ GOTO ถือว่าเป็นอันตราย" ทางด้านไวยากรณ์ cdmckay ให้ในคำตอบอื่นไวยากรณ์ที่เขาต้องการสำหรับยาม; ฉันไม่เห็นด้วยกับความเห็นของเขาว่ารูปแบบไหนอ่านได้มากกว่ากัน
John R.Strohm

0

ขณะใช้ยามโปรดปฏิบัติตามคำแนะนำบางประการเพื่อไม่ให้ผู้อ่านสับสน

  • ฟังก์ชันทำสิ่งหนึ่ง
  • ยามจะถูกนำมาใช้เป็นตรรกะแรกในฟังก์ชันเท่านั้น
  • unnestedส่วนหนึ่งที่มีฟังก์ชั่นของความตั้งใจหลัก

ตัวอย่าง

// guards point you to the core intent
void Remove(RayCastResult rayHit){

  if(rayHit== RayCastResult.Empty)
    return
    ;
  rayHit.Collider.Parent.Remove();
}

// no guards needed: function split into multiple cases
int WonOrLostMoney(int flaw)=>
  flaw==0 ? 100 :
  flaw<10 ? 30 :
  flaw<20 ? 0 :
  -20
;

-3

โยนข้อยกเว้นแทนที่จะไม่ส่งคืนอะไรเลยเมื่อวัตถุเป็นโมฆะเป็นต้น

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

แต่การกลับมาก่อนกำหนดไม่ใช่การปฏิบัติที่ไม่ดี


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