นี่เป็นสถานการณ์ทั่วไปและมีหลายวิธีที่จะจัดการกับมัน นี่คือความพยายามของฉันที่คำตอบที่ยอมรับได้ โปรดแสดงความคิดเห็นถ้าฉันพลาดอะไรและฉันจะโพสต์นี้ถึงวันที่
นี่คือลูกศร
สิ่งที่คุณกำลังพูดถึงนั้นเรียกว่าลูกศรต่อต้านรูปแบบลูกศรต่อต้านรูปแบบมันถูกเรียกว่าลูกศรเพราะห่วงโซ่ของ ifs ที่ซ้อนกันในรูปแบบบล็อกโค้ดที่ขยายไกลออกไปและไกลออกไปทางด้านขวาและจากนั้นกลับไปทางซ้ายสร้างลูกศรแบบภาพที่ "ชี้" ไปทางด้านขวาของบานหน้าต่างตัวแก้ไขโค้ด
แผ่ธนูกับ Guard
บางวิธีการทั่วไปของการหลีกเลี่ยงลูกศรที่จะกล่าวถึงที่นี่ วิธีที่พบมากที่สุดคือการใช้รูปแบบการป้องกันซึ่งในรหัสจะจัดการข้อยกเว้นการไหลก่อนแล้วจัดการการไหลขั้นพื้นฐานเช่นแทน
if (ok)
{
DoSomething();
}
else
{
_log.Error("oops");
return;
}
... คุณจะใช้ ....
if (!ok)
{
_log.Error("oops");
return;
}
DoSomething(); //notice how this is already farther to the left than the example above
เมื่อมียามแบบยาวชุดนี้จะทำให้รหัสแบนราบมากเนื่องจากการ์ดทั้งหมดปรากฏขึ้นไปทางด้านซ้ายและ ifs ของคุณไม่ซ้อนกัน นอกจากนี้คุณกำลังจับคู่ภาพเงื่อนไขตรรกะกับข้อผิดพลาดที่เกี่ยวข้องซึ่งทำให้ง่ายต่อการบอกสิ่งที่เกิดขึ้น:
ลูกศร:
ok = DoSomething1();
if (ok)
{
ok = DoSomething2();
if (ok)
{
ok = DoSomething3();
if (!ok)
{
_log.Error("oops"); //Tip of the Arrow
return;
}
}
else
{
_log.Error("oops");
return;
}
}
else
{
_log.Error("oops");
return;
}
ป้องกัน:
ok = DoSomething1();
if (!ok)
{
_log.Error("oops");
return;
}
ok = DoSomething2();
if (!ok)
{
_log.Error("oops");
return;
}
ok = DoSomething3();
if (!ok)
{
_log.Error("oops");
return;
}
ok = DoSomething4();
if (!ok)
{
_log.Error("oops");
return;
}
นี่เป็นวิธีที่อ่านง่ายและเป็นเชิงปริมาณ
- อักขระ {และ} สำหรับบล็อกตรรกะที่กำหนดนั้นอยู่ใกล้กันมากขึ้น
- จำนวนบริบททางจิตที่จำเป็นในการทำความเข้าใจกับบรรทัดใดบรรทัดหนึ่งมีขนาดเล็กกว่า
- ตรรกะทั้งหมดที่เกี่ยวข้องกับเงื่อนไข if น่าจะอยู่ในหน้าเดียวมากกว่า
- ความต้องการใช้ coder เพื่อเลื่อนหน้า / ติดตามตาลดลงอย่างมาก
วิธีเพิ่มรหัสทั่วไปในตอนท้าย
ปัญหาเกี่ยวกับรูปแบบของการ์ดรักษาความปลอดภัยนั้นขึ้นอยู่กับสิ่งที่เรียกว่า "การส่งคืนโอกาส" หรือ "ทางออกการฉวยโอกาส" มันแบ่งรูปแบบที่แต่ละฟังก์ชั่นและทุกคนควรมีจุดทางออกเดียว ปัญหานี้เกิดจากสองสาเหตุ:
- มันทำให้คนอื่นถูวิธีที่ผิดเช่นคนที่เรียนรู้ที่จะใช้รหัสบน Pascal ได้เรียนรู้ว่าการทำงานหนึ่ง = หนึ่งจุดทางออก
- มันไม่ได้ให้ส่วนของรหัสที่ดำเนินการเมื่อออกไม่ว่าสิ่งที่อยู่ในมือ
ด้านล่างฉันมีตัวเลือกบางอย่างสำหรับการแก้ไขข้อ จำกัด นี้โดยใช้คุณสมบัติภาษาหรือหลีกเลี่ยงปัญหาโดยสิ้นเชิง
ตัวเลือกที่ 1 คุณไม่สามารถทำได้: ใช้ finally
น่าเสียดายที่ในฐานะนักพัฒนา c ++ คุณไม่สามารถทำได้ แต่นี่เป็นคำตอบหมายเลขหนึ่งสำหรับภาษาที่มีคำหลักในที่สุดเนื่องจากนี่คือสิ่งที่มันเป็น
try
{
if (!ok)
{
_log.Error("oops");
return;
}
DoSomething(); //notice how this is already farther to the left than the example above
}
finally
{
DoSomethingNoMatterWhat();
}
ตัวเลือกที่ 2 หลีกเลี่ยงปัญหา: ปรับโครงสร้างการทำงานของคุณ
คุณสามารถหลีกเลี่ยงปัญหาโดยการแบ่งรหัสออกเป็นสองฟังก์ชัน วิธีนี้มีประโยชน์ในการทำงานกับภาษาใด ๆ และนอกจากนี้ยังสามารถลดความซับซ้อนของวัฏจักรซึ่งเป็นวิธีที่พิสูจน์แล้วว่าช่วยลดอัตราข้อบกพร่องของคุณและปรับปรุงความจำเพาะของการทดสอบหน่วยอัตโนมัติใด ๆ
นี่คือตัวอย่าง:
void OuterFunction()
{
DoSomethingIfPossible();
DoSomethingNoMatterWhat();
}
void DoSomethingIfPossible()
{
if (!ok)
{
_log.Error("Oops");
return;
}
DoSomething();
}
ตัวเลือก 3. เคล็ดลับภาษา: ใช้วนซ้ำปลอม
เคล็ดลับทั่วไปอีกอย่างหนึ่งที่ฉันเห็นคือใช้ขณะที่ (จริง) และหยุดพักตามที่แสดงในคำตอบอื่น ๆ
while(true)
{
if (!ok) break;
DoSomething();
break; //important
}
DoSomethingNoMatterWhat();
แม้ว่าสิ่งนี้จะ "ซื่อสัตย์" น้อยกว่าการใช้งานgoto
แต่ก็มีโอกาสน้อยที่จะถูกทำให้ยุ่งเหยิงเมื่อทำการปรับโครงสร้างใหม่เนื่องจากมันทำเครื่องหมายขอบเขตของขอบเขตตรรกะอย่างชัดเจน รหัสไร้เดียงสาที่ตัดและวางป้ายหรือของคุณgoto
ข้อความอาจทำให้เกิดปัญหาร้ายแรง! (และรูปแบบที่ตรงไปตรงมาเป็นเรื่องธรรมดามากตอนนี้ฉันคิดว่ามันชัดเจนสื่อสารความตั้งใจและดังนั้นจึงไม่ "ไม่น่าไว้วางใจ" เลย)
มีตัวแปรอื่น ๆ ของตัวเลือกนี้ ตัวอย่างเช่นหนึ่งสามารถใช้แทนswitch
while
โครงสร้างภาษาใด ๆ ที่มีbreak
คำหลักอาจจะทำงานได้
ตัวเลือก 4 ยกระดับวัฏจักรชีวิตของวัตถุ
อีกวิธีหนึ่งยกระดับวัฏจักรชีวิตของวัตถุ ใช้วัตถุบริบทเพื่อดำเนินการพารามิเตอร์ของคุณ (สิ่งที่ตัวอย่างไร้เดียงสาของเราอย่างน่าสงสัยขาด) และกำจัดมันเมื่อคุณทำเสร็จแล้ว
class MyContext
{
~MyContext()
{
DoSomethingNoMatterWhat();
}
}
void MainMethod()
{
MyContext myContext;
ok = DoSomething(myContext);
if (!ok)
{
_log.Error("Oops");
return;
}
ok = DoSomethingElse(myContext);
if (!ok)
{
_log.Error("Oops");
return;
}
ok = DoSomethingMore(myContext);
if (!ok)
{
_log.Error("Oops");
}
//DoSomethingNoMatterWhat will be called when myContext goes out of scope
}
หมายเหตุ: ให้แน่ใจว่าคุณเข้าใจวงจรชีวิตของภาษาที่คุณเลือก คุณต้องมีการรวบรวมขยะที่กำหนดขึ้นเพื่อให้สามารถใช้งานได้เช่นคุณต้องรู้ว่าจะมีการเรียกตัวทำลายเมื่อใด ในบางภาษาคุณจะต้องใช้Dispose
แทน destructor
ตัวเลือก 4.1 ยกระดับวัฏจักรของวัตถุ (รูปแบบตัวห่อ)
หากคุณกำลังจะใช้วิธีการเชิงวัตถุก็อาจทำถูกต้อง ตัวเลือกนี้ใช้คลาสเพื่อ "ห่อ" ทรัพยากรที่ต้องการล้างข้อมูลรวมถึงการดำเนินการอื่น ๆ
class MyWrapper
{
bool DoSomething() {...};
bool DoSomethingElse() {...}
void ~MyWapper()
{
DoSomethingNoMatterWhat();
}
}
void MainMethod()
{
bool ok = myWrapper.DoSomething();
if (!ok)
_log.Error("Oops");
return;
}
ok = myWrapper.DoSomethingElse();
if (!ok)
_log.Error("Oops");
return;
}
}
//DoSomethingNoMatterWhat will be called when myWrapper is destroyed
อีกครั้งให้แน่ใจว่าคุณเข้าใจวงจรชีวิตของวัตถุของคุณ
ตัวเลือกที่ 5 เคล็ดลับภาษา: ใช้การประเมินผลการลัดวงจร
อีกเทคนิคหนึ่งคือการใช้ประโยชน์จากการประเมินผลการลัดวงจร
if (DoSomething1() && DoSomething2() && DoSomething3())
{
DoSomething4();
}
DoSomethingNoMatterWhat();
โซลูชันนี้ใช้ประโยชน์จากวิธีการทำงานของผู้ให้บริการ เมื่อด้านซ้ายของ && ประเมินเป็นเท็จทางด้านขวาจะไม่ถูกประเมิน
เคล็ดลับนี้มีประโยชน์มากที่สุดเมื่อต้องการรหัสขนาดกะทัดรัดและเมื่อรหัสไม่น่าจะเห็นการบำรุงรักษามากเช่นคุณกำลังใช้อัลกอริทึมที่รู้จักกันดี สำหรับการเข้ารหัสทั่วไปเพิ่มเติมโครงสร้างของรหัสนี้มีความเปราะเกินไป แม้แต่การเปลี่ยนแปลงเล็กน้อยในตรรกะอาจทำให้เกิดการเขียนซ้ำทั้งหมด