ในขณะที่ (จริง) และวงแตก - รูปแบบการต่อต้าน?


32

พิจารณารหัสต่อไปนี้:

public void doSomething(int input)
{
   while(true)
   {
      TransformInSomeWay(input);

      if(ProcessingComplete(input))
         break;

      DoSomethingElseTo(input);
   }
}

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

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

ตอนนี้รหัสสามารถโครงสร้างดังนี้

public void doSomething(int input)
{
   TransformInSomeWay(input);

   while(!ProcessingComplete(input))
   {
      DoSomethingElseTo(input);
      TransformInSomeWay(input);
   }
}

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

คุณสามารถเขียนเช่น:

public void doSomething(int input)
{
   var complete = false;
   while(!complete)
   {
      TransformInSomeWay(input);

      complete = ProcessingComplete(input);

      if(!complete) 
      {
         DoSomethingElseTo(input);          
      }
   }
}

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

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

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


12
อาจเป็นไปได้ แต่ถึงแม้จะมีตัวอย่างโค้ดคำถามที่ถูกถามก็คือแนวความคิด IMO ที่มากขึ้นซึ่งสอดคล้องกับขอบเขตของ SE นี้มากกว่า รูปแบบหรือรูปแบบการต่อต้านถูกกล่าวถึงที่นี่บ่อยครั้งและนั่นเป็นคำถามที่แท้จริง การสร้างลูปเพื่อให้ทำงานได้อย่างไม่มีที่สิ้นสุดแล้วแยกออกมาเป็นสิ่งเลวร้ายหรือไม่?
KeithS

3
do ... untilสร้างยังเป็นประโยชน์สำหรับชนิดเหล่านี้ของลูปเนื่องจากพวกเขาไม่จำเป็นต้องมีการ "primed."
Blrfl

2
กรณีลูปและครึ่งยังขาดคำตอบที่ดีจริงๆ
Loren Pechtel

1
"อย่างไรก็ตามนี่เป็นการเรียกวิธีในรหัสซ้ำกับการละเมิด DRY" ฉันไม่เห็นด้วยกับการยืนยันนั้นและดูเหมือนว่ามีความเข้าใจผิดในหลักการ
Andy

1
นอกเหนือจากฉันพอใจที่ C-สไตล์ (;;) ซึ่งexplictelyหมายความว่า "ฉันมีห่วงและผมไม่ได้ตรวจสอบในงบวง" วิธีการแรกของคุณเป็นอย่างที่เหมาะสม คุณมีวง มีงานที่ต้องทำก่อนที่คุณจะตัดสินใจว่าจะออก จากนั้นคุณออกหากมีเงื่อนไขบางประการ จากนั้นคุณทำงานจริงและเริ่มต้นใหม่ นั่นเป็นเรื่องที่ดีเข้าใจง่ายมีเหตุผลไม่ซ้ำซ้อนและเข้าใจง่าย ตัวแปรที่สองนั้นแย่มากในขณะที่ตัวแปรที่สามนั้นไร้จุดหมาย เปลี่ยนเป็นน่ากลัวหากส่วนที่เหลือของลูปที่เข้าสู่ if นั้นยาวมาก
gnasher729

คำตอบ:


14

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


"if-statements และรหัสซ้ำซึ่งทั้งหมดมีแนวโน้มที่จะเกิดปัญหามากกว่า" - อ๋อเมื่อพยายามคนที่ฉันเรียกว่าเป็น "บั๊กในอนาคต"
Pat

13

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


7

ฉันมีปัญหาในการค้นหาวิธีแก้ปัญหาอื่นที่ดีกว่าอันที่มีการหยุดพัก ฉันชอบวิธี Ada ที่ยอมให้ทำ

loop
  TransformInSomeWay(input);
exit when ProcessingComplete(input);
  DoSomethingElseTo(input);
end loop;

แต่โซลูชันตัวแบ่งคือการเรนเดอร์ที่สะอาดที่สุด

POV ของฉันคือเมื่อมีโครงสร้างการควบคุมที่ขาดหายไปโซลูชันที่สะอาดที่สุดคือเลียนแบบโครงสร้างการควบคุมอื่น ๆ โดยไม่ใช้การไหลของข้อมูล (และในทำนองเดียวกันเมื่อฉันมีปัญหาการไหลของข้อมูลที่ต้องการโซลูชันการไหลของข้อมูลที่บริสุทธิ์ไปยังสิ่งที่เกี่ยวข้องกับโครงสร้างการควบคุม *)

เป็นการยากกว่าที่จะทราบว่าคุณจะออกจากลูปอย่างไร

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

อันไหนของ

while (true) {
   XXXX
if (YYY) break;
   ZZZZ;
}

และ

while (TTTT) {
    XXXX
    if (YYYY) {
        ZZZZ
    }
}

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


1
โอ้ดูภาษาที่ไม่สนับสนุนโดยตรงลูปกลางทางออก! :)
mjfgates

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

3

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

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


2

ทางเลือกของคุณไม่เลวเท่าที่คุณคิด รหัสที่ดีควร:

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

  2. ระบุอย่างชัดเจนว่าอะไรคือลำดับของการดำเนินการ นี่คือที่วางการดำเนินการตัวเลือกที่ 3 ( DoSomethingElseTo(input)) ภายในถ้าเป็นความคิดที่ดี

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


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

ฉันไม่สามารถโทรหาคุณผิด :-) นี่เป็นคำถามความเห็นและหากทักษะที่ดีมาจากการขัดสีทองเหลือง: เยี่ยมมาก
Pat

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

2

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

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

ฉันกำลังเผชิญกับปัญหาที่คล้ายกัน:

$i = 0
$key = $str.'0';
$key1 = $str1.'0';
while (isset($array][$key] || isset($array][$key1])
{
    if (isset($array][$key]))
    {
        // Code
    }
    else
    {
        // Code
    }
    $key = $str.++$i;
    $key1 = $str1.$i;
}

การใช้do ... while(true)หลีกเลี่ยงisset($array][$key]การถูกเรียกสองครั้งโดยไม่จำเป็นรหัสจะสั้นกว่าทำความสะอาดและอ่านง่ายขึ้น else break;ในตอนท้ายไม่มีความเสี่ยงที่จะเกิดบั๊กแบบไม่สิ้นสุด

$i = -1;
do
{
    $key = $str.++$i;
    $key1 = $str1.$i;
    if (isset($array[$key]))
    {
        // Code
    }
    elseif (isset($array[$key1]))
    {
        // Code
    }
    else
        break;
} while (true);

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


0

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

(โปรดทราบว่าสิ่งนี้รวมถึงการปลดลูปบางส่วนและเลื่อนตัวลูปลงเพื่อให้ลูปเริ่มต้นขึ้นพร้อมกับการทดสอบตามเงื่อนไข)

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


0

คุณสามารถไปกับสิ่งนี้:

public void doSomething(int input)
{
   while(TransformInSomeWay(input), !ProcessingComplete(input))
   {
      DoSomethingElseTo(input);
   }
}

หรือน้อยกว่าที่สับสน:

bool TransformAndCheckCompletion(int &input)
{
   TransformInSomeWay(input);
   return ProcessingComplete(input))
}

public void doSomething(int input)
{
   while(TransformAndCheckCompletion(input))
   {
      DoSomethingElseTo(input);
   }
}

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

5
นิพจน์จุลภาคในสภาพลูป ... แย่มาก คุณฉลาดเกินไป และใช้งานได้ในกรณีที่ไม่สำคัญนี้ซึ่ง TransformInSomeWay สามารถแสดงเป็นนิพจน์ได้
gnasher729

0

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

L: for (;;) {
    if (someBool) {
        someOtherBool = doSomeWork();
        if (someOtherBool) {
            doFinalWork();
            break L;
        }
        someOtherBool2 = doSomeWork2();
        if (someOtherBool2) {
            doFinalWork2();
            break L;
        }
        someOtherBool3 = doSomeWork3();
        if (someOtherBool3) {
            doFinalWork3();
            break L;
        }
    }
    bitOfData = getTheData();
    throw new SomeException("Unable to do something for some reason.", bitOfData);
}
progressOntoOtherThings();

ในการทำตรรกะนี้โดยไม่วนซ้ำฉันจะต้องทำสิ่งต่อไปนี้:

void method() {
    if (!someBool) {
        throwingMethod();
    }
    someOtherBool = doSomeWork();
    if (someOtherBool) {
        doFinalWork();
    } else {
        someOtherBool2 = doSomeWork2();
        if (someOtherBool2) {
            doFinalWork2();
        } else {
            someOtherBool3 = doSomeWork3();
            if (someOtherBool3) {
                doFinalWork3();
            } else {
                throwingMethod();
            }
        }
    }
    progressOntoOtherThings();
}
void throwingMethod() throws SomeException {
        bitOfData = getTheData();
        throw new SomeException("Unable to do something for some reason.", bitOfData);
}

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


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