กลับคำสั่ง“ ถ้า” เพื่อลดการซ้อน


272

เมื่อฉันรันReSharperในรหัสของฉันตัวอย่างเช่น:

    if (some condition)
    {
        Some code...            
    }

ReSharper ให้คำเตือนข้างต้น (Invert "if" เพื่อลดการซ้อน) และแนะนำการแก้ไขต่อไปนี้:

   if (!some condition) return;
   Some code...

ฉันต้องการที่จะเข้าใจว่าทำไมถึงดีกว่า ฉันมักจะคิดว่าการใช้ "ส่งคืน" ตรงกลางของวิธีการที่มีปัญหานั้นค่อนข้างเหมือนกับ "goto"


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

36
ไม่มันจะไม่ทำอะไรเพื่อประสิทธิภาพ
Seth Carnegie

3
ฉันถูกล่อลวงให้โยน ArgumentException หากวิธีการของฉันถูกส่งผ่านข้อมูลที่ไม่ดีแทน
asawyer

1
@asawyer ใช่มีทั้งการสนทนาด้านข้างที่นี่เกี่ยวกับฟังก์ชั่นที่ให้อภัยอินพุตที่ไร้สาระ - ซึ่งต่างจากการใช้ความล้มเหลวในการยืนยัน การเขียน Solid Code ทำให้ฉันลืมตาได้ ในกรณีนี้มันจะเป็นอย่างASSERT( exampleParam > 0 )นี้
Greg Hendershott

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

คำตอบ:


296

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

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

ในกรณีนี้หาก_isDeadเป็นจริงเราสามารถออกจากวิธีการได้ทันที มันอาจจะดีกว่าที่จะจัดโครงสร้างด้วยวิธีนี้แทน:

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

    return normalPayAmount();
};   

ฉันเลือกรหัสนี้จากแคตตาล็อก refactoring การเปลี่ยนโครงสร้างใหม่นี้เรียกว่า: แทนที่เงื่อนไขที่ซ้อนกันด้วย Guard Clauses


13
นี่เป็นตัวอย่างที่ดีจริงๆ! รหัส refactored อ่านเหมือนคำสั่งกรณี
Otherside

16
อาจเป็นเรื่องของรสนิยม: ฉันขอแนะนำให้เปลี่ยนลำดับที่ 2 และ 3 "ถ้า" เป็น "อื่น ๆ " เพื่อเพิ่มความสามารถในการอ่านได้มากขึ้น หากมีใครมองเห็นคำสั่ง "ส่งคืน" ก็จะยังคงชัดเจนว่ากรณีต่อไปนี้จะถูกตรวจสอบเฉพาะในกรณีที่ก่อนหน้านี้ล้มเหลวนั่นคือลำดับของการตรวจสอบที่มีความสำคัญ
foraidt

2
ในตัวอย่างง่าย ๆ นี้ id เห็นด้วยกับวิธีที่ 2 ดีกว่า แต่เพราะมันเห็นได้ชัดว่าเกิดอะไรขึ้น
Andrew Bullock

ตอนนี้เราจะนำโค้ด jop ที่เขียนและป้องกันไม่ให้สตูดิโอภาพแยกออกจากกันและวางผลตอบแทนในบรรทัดแยกกันได้อย่างไร มันทำให้ฉันหงุดหงิดเลยว่ามันฟอร์แมตโค้ดใหม่ ทำให้โค้ดนี้อ่านง่ายมาก UGLY
ทำเครื่องหมาย T

@ Mark T มีการตั้งค่าใน Visual Studio เพื่อป้องกันไม่ให้รหัสนั้นเสียหาย
แอรอนสมิ ธ

333

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

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

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

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


41
จุดทางออกเดียว? ใครต้องการมัน
sq33G

3
@ sq33G: คำถามเกี่ยวกับ SESE (และคำตอบของหลักสูตร) ​​นั้นยอดเยี่ยม ขอบคุณสำหรับลิงค์!
Jon

ฉันได้ยินคำตอบเสมอว่ามีบางคนที่สนับสนุนการออกจากจุดเดียว แต่ฉันไม่เคยเห็นใครบางคนเรียกร้องสิ่งนั้นโดยเฉพาะอย่างยิ่งในภาษาเช่น C #
Thomas Bonini

1
@AndreasBonini: การขาดหลักฐานไม่ใช่หลักฐานการขาด :-)
Jon

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

102

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

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

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

สำหรับเชิงลบเกี่ยวกับการคืนค่าหลาย ๆ ครั้งจากวิธีการ - debuggers นั้นค่อนข้างทรงพลังในตอนนี้และมันง่ายมากที่จะค้นหาว่าที่ไหนและเมื่อไรที่ฟังก์ชั่นหนึ่งกำลังกลับมา

การได้รับผลตอบแทนหลายครั้งในฟังก์ชั่นจะไม่ส่งผลกระทบต่องานของโปรแกรมเมอร์

การอ่านรหัสไม่ดีจะ


2
การส่งคืนหลายครั้งในฟังก์ชั่นจะมีผลต่อโปรแกรมเมอร์การบำรุง เมื่อทำการดีบั๊กฟังก์ชั่นการค้นหาค่าตอบแทนอันธพาลผู้ดูแลจะต้องใส่จุดพักในทุกสถานที่ที่สามารถกลับมา
EvilTeach

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

3
เห็นด้วยกับจอห์นที่นี่ ... ฉันไม่เห็นปัญหาในการก้าวผ่านฟังก์ชั่นทั้งหมด
Nailer

5
@Nailer ช้ามากฉันเห็นด้วยโดยเฉพาะสาเหตุถ้าฟังก์ชั่นใหญ่เกินไปที่จะก้าวผ่านมันควรจะแยกออกเป็นหลาย ๆ ฟังก์ชั่นต่อไป!
Aidiakapi

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

70

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

public void myfunction(double exampleParam){
    if(exampleParam > 0){
        //Body will *not* be executed if Double.IsNan(exampleParam)
    }
}

คมชัดว่ามีดูเหมือนผกผันเทียบเท่า:

public void myfunction(double exampleParam){
    if(exampleParam <= 0)
        return;
    //Body *will* be executed if Double.IsNan(exampleParam)
}

ดังนั้นในบางสถานการณ์สิ่งที่ดูเหมือนว่าจะเป็นกลับอย่างถูกต้องifอาจจะไม่


4
ควรสังเกตว่าตัวแก้ไขด่วนของตัวแก้ไข res แทน exampleParam> 0 เป็น exampleParam <0 ไม่ใช่ exampleParam <= 0 ซึ่งจับฉันออก
nickd

1
และนี่คือสาเหตุที่เช็คนั้นควรอยู่ข้างหน้าและส่งคืนเช่นกันเนื่องจากผู้พัฒนาคิดว่า nan ควรทำให้เกิดการประกันตัว
user441521

51

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

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

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


6
คุณเพิ่งค้นพบว่าทำไมข้อยกเว้นถึง UglyAndEvil [tm] ... ;-) ข้อยกเว้นเป็น gotos ในการปลอมตัวแฟนซีราคาแพง
EricSchaefer

16
@Eric คุณต้องมีรหัสที่ไม่ดีเป็นพิเศษ ค่อนข้างชัดเจนเมื่อใช้อย่างไม่ถูกต้องและโดยทั่วไปจะอนุญาตให้คุณเขียนรหัสที่มีคุณภาพสูงขึ้น
Robert Paulson

นี่คือคำตอบที่ฉันต้องการจริงๆ! มีคำตอบที่ดีที่นี่ แต่ส่วนใหญ่มุ่งเน้นไปที่ตัวอย่างไม่ใช่เหตุผลที่แท้จริงว่าทำไมคำแนะนำนี้จึงเกิด
Gustavo Mori

นี่คือคำตอบที่ดีที่สุดเพราะมันอธิบายว่าทำไมการโต้เถียงทั้งหมดนี้เกิดขึ้นตั้งแต่แรก
Buttle Butkus

If you've got deep nesting, maybe your function is trying to do too many things.=> นี่ไม่ใช่ผลลัพธ์ที่ถูกต้องจากวลีก่อนหน้าของคุณ เพราะก่อนที่คุณจะบอกว่าคุณสามารถ refactor พฤติกรรมที่มีรหัส C เป็นพฤติกรรมที่มีรหัส D. รหัส D คือสะอาดได้รับ แต่ "หลายสิ่งมากเกินไป" หมายถึงพฤติกรรมที่ไม่ได้เปลี่ยน ดังนั้นคุณไม่สมเหตุสมผลกับข้อสรุปนี้
v.oddou

30

ฉันต้องการเพิ่มว่ามีชื่อสำหรับผู้ที่กลับหัวกลับหางหาก - คำสั่งป้องกัน ฉันใช้มันทุกครั้งที่ทำได้

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

http://c2.com/cgi/wiki?GuardClause


2
เผง มันเป็นมากกว่าการฟื้นฟู ช่วยให้อ่านรหัสได้ง่ายขึ้นตามที่คุณกล่าวถึง
rpattabi

'การกลับมา' นั้นไม่เพียงพอและน่ากลัวสำหรับประโยคที่เป็นยาม ฉันจะทำ: ถ้า (อดีต <= 0) โยน WrongParamValueEx ("[MethodName] อินพุตที่ไม่ถูกต้องพารามิเตอร์ 1 ค่า {0} ... และคุณจะต้องตรวจสอบข้อยกเว้นและเขียนลงในบันทึกแอปของคุณ
JohnJohnGa

3
@จอห์น. คุณพูดถูกถ้าผิดจริง แต่บ่อยครั้งที่มันไม่ใช่ และแทนที่จะตรวจสอบบางสิ่งในทุกสถานที่ซึ่งวิธีการที่เรียกว่า method เพียงตรวจสอบและกลับมาทำอะไรเลย
Piotr Perak

3
ฉันเกลียดที่จะอ่านวิธีการที่มีสองหน้าจอของรหัสระยะเวลาifs หรือไม่ lol
jpmc26

1
โดยส่วนตัวแล้วฉันชอบผลตอบแทนเร็วเพราะเหตุผลนี้ (เช่น: หลีกเลี่ยงการทำรัง) อย่างไรก็ตามนี่ไม่เกี่ยวข้องกับคำถามดั้งเดิมเกี่ยวกับประสิทธิภาพและน่าจะเป็นความคิดเห็น
Patrick M

22

มันไม่เพียงส่งผลกระทบต่อความสวยงาม แต่ยังป้องกันการซ้อนโค้ด

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


18

แน่นอนว่าเป็นเรื่องส่วนตัว แต่ฉันคิดว่ามันจะดีขึ้นอย่างมากในสองประเด็น:

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

15

จุดคืนสินค้าหลายจุดเป็นปัญหาใน C (และในระดับที่น้อยกว่า C ++) เนื่องจากพวกเขาบังคับให้คุณทำซ้ำรหัสทำความสะอาดก่อนจุดส่งคืนแต่ละจุด ด้วยการรวบรวมขยะtry| finallyสร้างและusingบล็อกไม่มีเหตุผลจริงๆที่คุณควรกลัวพวกเขา

ในที่สุดมันก็มาถึงสิ่งที่คุณและเพื่อนร่วมงานของคุณอ่านง่ายขึ้น


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

1
ข่าว: จริง ๆ แล้วฉันได้พบเหตุผลที่เป็นประโยชน์มากว่าทำไมการพักและคืนก่อนกำหนดไม่ดีและเป็นผลโดยตรงจากสิ่งที่ฉันพูดการวิเคราะห์แบบคงที่ ที่นี่คุณจะไป Intel C ++ คอมไพเลอร์ใช้กระดาษ: d3f8ykwhia686p.cloudfront.net/1live/intel/... วลีสำคัญ:a loop that is not vectorizable due to a second data-dependent exit
v.oddou

12

ประสิทธิภาพที่ชาญฉลาดจะไม่มีความแตกต่างที่ชัดเจนระหว่างสองแนวทาง

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

มีโรงเรียนแห่งความคิดที่แข่งขันกันว่าจะเลือกวิธีไหนดีกว่า

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

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


11

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

if (something) {
    // a lot of indented code
}

คุณย้อนกลับเงื่อนไขและแบ่งถ้าเงื่อนไขที่ตรงกันข้ามนั้นเป็นจริง

if (!something) return false; // or another value to show your other code the function did not execute

// all the code from before, save a lot of tabs

returngotoไม่มีที่ไหนเลยใกล้สกปรกเหมือน อนุญาตให้คุณส่งค่าเพื่อแสดงรหัสที่เหลือที่ฟังก์ชันไม่สามารถทำงานได้

คุณจะเห็นตัวอย่างที่ดีที่สุดของสิ่งที่สามารถนำไปใช้ในสภาวะซ้อน:

if (something) {
    do-something();
    if (something-else) {
        do-another-thing();
    } else {
        do-something-else();
    }
}

VS:

if (!something) return;
do-something();

if (!something-else) return do-something-else();
do-another-thing();

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

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


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

@Otherside: เห็นด้วยอย่างสมบูรณ์ ผลตอบแทนที่เขียนขึ้นในแบบอนุกรมทำให้สมองของคุณต้องมีเส้นทางที่เป็นไปได้ เมื่อ if-tree สามารถแมปเข้ากับตรรกะ transicent ได้โดยตรงตัวอย่างเช่นมันสามารถคอมไพล์เป็นเสียงกระเพื่อมได้ แต่แฟชั่นการส่งคืนแบบอนุกรมจะต้องใช้คอมไพเลอร์ยากกว่านั้นมาก จุดที่เห็นได้ชัดว่าไม่มีอะไรเกี่ยวข้องกับสถานการณ์สมมุตินี้มันเกี่ยวข้องกับการวิเคราะห์รหัสโอกาสในการปรับให้เหมาะสมการพิสูจน์การแก้ไขการพิสูจน์การเลิกจ้างและการตรวจจับค่าคงที่
v.oddou

1
ไม่มีวิธีที่ทุกคนพบว่าการอ่านโค้ดด้วยรังมากกว่าง่ายกว่า ยิ่งบล็อกที่มีลักษณะคล้ายกันคืออ่านได้ง่ายขึ้น ไม่มีวิธีตัวอย่างแรกที่นี่ฉันอ่านง่ายขึ้นและมันไปเพียง 2 รังในเมื่อรหัสส่วนใหญ่เช่นนี้ในโลกแห่งความจริงไปลึกและทุกรังจะต้องใช้พลังสมองมากขึ้นที่จะปฏิบัติตาม
user441521

1
หากการทำรังลึกสองเท่าคุณต้องใช้เวลานานเท่าไรในการตอบคำถาม: "คุณจะทำอะไรอย่างอื่น" เมื่อไหร่? ฉันคิดว่าคุณจะกดยากที่จะตอบโดยไม่ต้องใช้ปากกาและกระดาษ แต่คุณสามารถตอบได้อย่างง่ายดายถ้าทุกอย่างเป็นไปตามคำสั่ง
David Storfer

9

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


8

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

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


7

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

เช่น.

 bool PerformDefaultOperation()
 {
      bool succeeded = false;

      DataStructure defaultParameters;
      if ((defaultParameters = this.GetApplicationDefaults()) != null)
      {
           succeeded = this.DoSomething(defaultParameters);
      }

      return succeeded;
 }

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


4
บูล PerformDefaultOperation () {DataStructure defaultParameters = this.GetApplicationDefaults (); return (defaultParameters! = NULL && this.DoSomething (defaultParameters);} ที่นั่นแก้ไขมันให้คุณ :)
tchen

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

5

เหตุผลที่ดีมากเกี่ยวกับวิธีการรหัสที่มีลักษณะดังนี้ แต่แล้วผลลัพธ์ล่ะ

ลองดูโค้ด C # และรูปแบบ IL ที่คอมไพล์แล้ว:


using System;

public class Test {
    public static void Main(string[] args) {
        if (args.Length == 0) return;
        if ((args.Length+2)/3 == 5) return;
        Console.WriteLine("hey!!!");
    }
}

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

รหัส IL ที่สร้างขึ้นทำดังต่อไปนี้:

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

ดังนั้นดูเหมือนว่ารหัสจะข้ามไปยังจุดสิ้นสุด จะเกิดอะไรขึ้นถ้าหากเราทำรหัสปกติด้วยรหัสที่ซ้อนกัน?

using System;

public class Test {
    public static void Main(string[] args) {
        if (args.Length != 0 && (args.Length+2)/3 != 5) 
        {
            Console.WriteLine("hey!!!");
        }
    }
}

ผลลัพธ์ค่อนข้างคล้ายคลึงกันในคำแนะนำของ IL ความแตกต่างคือก่อนที่จะมีการข้ามไปตามเงื่อนไข: ถ้าเป็นเท็จไปที่ชิ้นส่วนของรหัสต่อไปถ้าเป็นจริงไปแล้วก็จบ และตอนนี้รหัส IL จะไหลได้ดีขึ้นและมีการข้าม 3 ครั้ง (คอมไพเลอร์ปรับให้เหมาะสมเล็กน้อย): 1. การกระโดดครั้งแรก: เมื่อความยาวเท่ากับ 0 ถึงส่วนที่รหัสกระโดดอีกครั้ง (การกระโดดครั้งที่สาม) ถึงจุดสิ้นสุด 2. ที่สอง: อยู่ตรงกลางของเงื่อนไขที่สองเพื่อหลีกเลี่ยงหนึ่งคำสั่ง 3. ประการที่สาม: หากเงื่อนไขที่สองเป็นเท็จให้ข้ามไปยังจุดสิ้นสุด

อย่างไรก็ตามตัวนับโปรแกรมจะกระโดดเสมอ


5
นี่เป็นข้อมูลที่ดี - แต่โดยส่วนตัวแล้วฉันไม่สนหรอกถ้า IL "ดีขึ้น" เพราะคนที่จะจัดการรหัสจะไม่เห็น IL
JohnIdol

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

5

ในทางทฤษฎีการกลับด้านifอาจนำไปสู่ประสิทธิภาพที่ดีขึ้นถ้ามันเพิ่มอัตราการตีสาขา ในทางปฏิบัติฉันคิดว่ามันยากมากที่จะรู้ว่าการทำนายสาขาจะทำงานอย่างไรโดยเฉพาะอย่างยิ่งหลังจากการรวบรวมดังนั้นฉันจะไม่ทำในการพัฒนาแบบวันต่อวันยกเว้นว่าฉันกำลังเขียนรหัสชุดประกอบ

เพิ่มเติมเกี่ยวกับสาขาการทำนายที่นี่


4

นั่นเป็นเพียงการโต้เถียง ไม่มี "ข้อตกลงระหว่างโปรแกรมเมอร์" ในคำถามคืนต้น มันเป็นอัตนัยเสมอเท่าที่ฉันรู้

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

ฉันไม่คิดว่าคุณจะได้คำตอบที่เป็นข้อสรุปสำหรับคำถามนี้


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

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

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

3

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

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

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

ดังนั้นนี่อาจเป็นสถานการณ์อื่นที่ ifs กลับหัวสามารถมีส่วนร่วมในรหัสที่ชัดเจนขึ้น


3

การหลีกเลี่ยงจุดออกหลายจุดสามารถทำให้ประสิทธิภาพเพิ่มขึ้น ฉันไม่แน่ใจเกี่ยวกับ C # แต่ใน C ++ การปรับค่าส่งคืนที่มีชื่อ (คัดลอก Elision, ISO C ++ '03 12.8 / 15) ขึ้นอยู่กับการมีจุดทางออกเดียว การเพิ่มประสิทธิภาพนี้หลีกเลี่ยงการคัดลอกการสร้างค่าตอบแทนของคุณ (ในตัวอย่างเฉพาะของคุณซึ่งไม่สำคัญ) สิ่งนี้อาจนำไปสู่การเพิ่มขึ้นอย่างมากของประสิทธิภาพการทำงานในลูปแน่นในขณะที่คุณบันทึกคอนสตรัคเตอร์และ destructor ในแต่ละครั้งที่มีการเรียกใช้ฟังก์ชัน

แต่สำหรับ 99% ของกรณีที่บันทึกการเรียกตัวสร้างและตัวทำลายเพิ่มเติมนั้นไม่คุ้มกับการสูญเสียifบล็อกที่ซ้อนกันที่สามารถอ่านได้ซึ่งแนะนำ


2
ที่นั่น ฉันใช้เวลาอ่าน 3 ครั้งนานนับสิบคำตอบใน 3 คำถามที่ถามในสิ่งเดียวกัน (2 ซึ่งถูกแช่แข็ง) เพื่อหาคนพูดถึง NRVO ในที่สุด gee ... ขอบคุณ
v.oddou

2

มันเป็นเรื่องของความเห็น

วิธีการปกติของฉันจะหลีกเลี่ยงบรรทัดเดียว ifs และส่งกลับในระหว่างวิธีการ

คุณไม่ต้องการให้มีบรรทัดเหมือนที่แสดงทุกที่ในวิธีการของคุณ แต่มีบางอย่างที่จะกล่าวเพื่อตรวจสอบสมมติฐานจำนวนมากที่ด้านบนของวิธีการของคุณและทำงานจริงของคุณหากพวกเขาผ่าน


2

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

หากคุณคืน returnValue จริง ๆ แล้วการทำรังเป็นวิธีที่ดีกว่าที่จะทำให้คุณคืน returnValue ได้ในที่เดียว (ในตอนท้าย - duh) และอาจทำให้โค้ดของคุณสามารถบำรุงรักษาได้ดีขึ้นในหลายกรณี


1

ฉันไม่แน่ใจ แต่ฉันคิดว่า R # พยายามหลีกเลี่ยงการกระโดดไกล เมื่อคุณมี IF-ELSE คอมไพเลอร์ก็ทำสิ่งนี้:

เงื่อนไขเป็นเท็จ -> ข้ามไปที่ false_condition_label

true_condition_label: คำแนะนำ 1 ... คำแนะนำ _n

false_condition_label: คำแนะนำ 1 ... คำแนะนำ _n

จบบล็อก

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


0

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


0

ความคิดของฉันคือการส่งคืน "กลางฟังก์ชัน" ไม่ควรเป็น "อัตนัย" เหตุผลค่อนข้างง่ายใช้รหัสนี้:

    ฟังก์ชัน do_something (ข้อมูล) {

      if (! is_valid_data (data)) 
            กลับเท็จ


       do_something_that_take_an_hour (ข้อมูล);

       istance = object_with_very_painful_constructor ใหม่ (ข้อมูล);

          ถ้า (istance ไม่ถูกต้อง) {
               ข้อความผิดพลาด( );
                กลับมา;

          }
       connect_to_database ();
       get_some_other_data ();
       กลับ;
    }

บางทีการ "ย้อนกลับ" ครั้งแรกอาจไม่ง่ายนัก แต่ก็ประหยัดจริงๆ มี "ความคิด" จำนวนมากเกินไปเกี่ยวกับรหัสที่สะอาดที่ต้องการเพียงการฝึกฝนให้มากขึ้น


ด้วยภาษายกเว้นคุณจะไม่มีปัญหาเหล่านี้
user441521

0

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


การอ่านคำตอบของ graffic มันฟังดูคล้ายกับโค้ดซ้อนกันที่ถูกปรับให้เหมาะสมโดยคอมไพเลอร์ในวิธีที่โค้ดที่เรียกใช้นั้นเร็วกว่าการใช้ผลตอบแทนหลายรายการ อย่างไรก็ตามแม้ว่าผลตอบแทนหลายเร็ว, การเพิ่มประสิทธิภาพนี้อาจจะไม่เพิ่มความเร็วในการใช้งานของคุณมากว่า ... :)
hangy

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