มีเหตุผลที่ดีหรือไม่ที่จะเป็นการดีกว่าที่จะมีคำสั่ง return หนึ่งคำในฟังก์ชัน?
หรือมันโอเคที่จะกลับจากฟังก์ชั่นทันทีที่มันถูกต้องตามหลักเหตุผลในการทำเช่นนั้นความหมายอาจมีหลายคำสั่ง return ในฟังก์ชั่น?
มีเหตุผลที่ดีหรือไม่ที่จะเป็นการดีกว่าที่จะมีคำสั่ง return หนึ่งคำในฟังก์ชัน?
หรือมันโอเคที่จะกลับจากฟังก์ชั่นทันทีที่มันถูกต้องตามหลักเหตุผลในการทำเช่นนั้นความหมายอาจมีหลายคำสั่ง return ในฟังก์ชั่น?
คำตอบ:
ฉันมักจะมีหลายข้อความในช่วงเริ่มต้นของวิธีการที่จะกลับมาในสถานการณ์ "ง่าย" ตัวอย่างเช่นสิ่งนี้:
public void DoStuff(Foo foo)
{
if (foo != null)
{
...
}
}
... สามารถทำให้อ่านได้มากขึ้น (IMHO) เช่นนี้
public void DoStuff(Foo foo)
{
if (foo == null) return;
...
}
ใช่ฉันคิดว่ามันดีที่มี "exit points" หลายอันจาก function / method
DoStuff() { DoStuffInner(); IncreaseStuffCallCounter(); }
ไม่มีใครพูดถึงหรืออ้างถึงCode Completeดังนั้นฉันจะทำ
ลดจำนวนผลตอบแทนในแต่ละขั้นตอนให้น้อยที่สุด เป็นการยากที่จะเข้าใจกิจวัตรประจำวันถ้าอ่านที่ด้านล่างคุณจะไม่ทราบถึงความเป็นไปได้ที่จะส่งคืนสิ่งที่อยู่ด้านบน
ใช้ผลตอบแทนเมื่อมันช่วยเพิ่มความสามารถในการอ่าน ในกิจวัตรบางอย่างเมื่อคุณรู้คำตอบแล้วคุณต้องการกลับไปที่รูทีนการโทรทันที หากรูทีนถูกกำหนดในลักษณะที่ไม่ต้องล้างข้อมูลใด ๆ การไม่ส่งคืนทันทีหมายความว่าคุณต้องเขียนโค้ดเพิ่มเติม
ฉันจะบอกว่ามันไม่ฉลาดเลยที่จะตัดสินโดยพลการต่อหลาย ๆ จุดเพราะฉันพบว่าเทคนิคนี้มีประโยชน์ในทางปฏิบัติซ้ำแล้วซ้ำอีกในความเป็นจริงฉันมักจะปรับรหัสที่มีอยู่ให้เป็นจุดออกหลายจุดเพื่อความชัดเจน เราสามารถเปรียบเทียบทั้งสองวิธีดังนี้: -
string fooBar(string s, int? i) {
string ret = "";
if(!string.IsNullOrEmpty(s) && i != null) {
var res = someFunction(s, i);
bool passed = true;
foreach(var r in res) {
if(!r.Passed) {
passed = false;
break;
}
}
if(passed) {
// Rest of code...
}
}
return ret;
}
เปรียบเทียบสิ่งนี้กับโค้ดที่อนุญาตให้มีจุดออกหลายจุด: -
string fooBar(string s, int? i) {
var ret = "";
if(string.IsNullOrEmpty(s) || i == null) return null;
var res = someFunction(s, i);
foreach(var r in res) {
if(!r.Passed) return null;
}
// Rest of code...
return ret;
}
ฉันคิดว่าหลังมีความชัดเจนมาก เท่าที่ฉันสามารถบอกได้ว่าการวิพากษ์วิจารณ์ของจุดออกหลายจุดนั้นเป็นมุมมองที่ค่อนข้างล้าสมัยในทุกวันนี้
ขณะนี้ฉันกำลังทำงานกับ codebase ซึ่งคนสองคนกำลังทำอยู่นั้นสมัครสมาชิกกับทฤษฎี "จุดเดียวของทางออก" และฉันสามารถบอกคุณได้ว่าจากประสบการณ์มันเป็นการปฏิบัติที่น่ากลัวอย่างยิ่ง มันทำให้รหัสยากมากที่จะรักษาและฉันจะแสดงสาเหตุ
ด้วยทฤษฎี "จุดเดียวของทางออก" คุณต้องปิดท้ายด้วยโค้ดที่มีลักษณะดังนี้:
function()
{
HRESULT error = S_OK;
if(SUCCEEDED(Operation1()))
{
if(SUCCEEDED(Operation2()))
{
if(SUCCEEDED(Operation3()))
{
if(SUCCEEDED(Operation4()))
{
}
else
{
error = OPERATION4FAILED;
}
}
else
{
error = OPERATION3FAILED;
}
}
else
{
error = OPERATION2FAILED;
}
}
else
{
error = OPERATION1FAILED;
}
return error;
}
สิ่งนี้ไม่เพียง แต่จะทำให้โค้ดยากที่จะติดตาม แต่ตอนนี้พูดในภายหลังว่าคุณจำเป็นต้องย้อนกลับไปและเพิ่มการดำเนินการระหว่าง 1 และ 2 คุณต้องเยื้องฟังก์ชัน freaking ทั้งหมดและขอให้โชคดี เงื่อนไข if / else และวงเล็บปีกกาของคุณถูกจับคู่อย่างเหมาะสม
วิธีนี้ทำให้การบำรุงรักษารหัสทำได้ยากมากและเกิดข้อผิดพลาดได้ง่าย
การเขียนโปรแกรมที่มีโครงสร้างบอกว่าคุณควรจะมีคำสั่งคืนหนึ่งต่อฟังก์ชั่นเท่านั้น นี่คือการจำกัดความซับซ้อน หลายคนเช่น Martin Fowler ยืนยันว่ามันง่ายกว่าที่จะเขียนฟังก์ชันที่มีคำสั่ง return หลายคำสั่ง เขานำเสนอข้อโต้แย้งนี้ในหนังสือการปรับโครงสร้างแบบดั้งเดิมที่เขาเขียน วิธีนี้ใช้งานได้ดีถ้าคุณทำตามคำแนะนำอื่น ๆ ฉันเห็นด้วยกับมุมมองนี้และมีเพียงครูสอนเขียนโปรแกรมที่มีโครงสร้างที่เข้มงวดเท่านั้นที่จะปฏิบัติตามคำสั่งการส่งคืนเดียวต่อฟังก์ชั่น
GOTO
เพื่อย้ายการควบคุมการไหลแม้ว่าจะมีฟังก์ชั่นอยู่ก็ตาม มันไม่เคยบอกว่า "ไม่เคยใช้GOTO
"
ดังที่เบ็คเบ็คบันทึกเมื่อพูดถึงคำสั่งป้องกันในรูปแบบการนำไปปฏิบัติทำให้รูทีนมีจุดเข้าและออกเพียงจุดเดียว ...
"เพื่อป้องกันความสับสนที่อาจเกิดขึ้นเมื่อกระโดดเข้าและออกจากสถานที่หลายแห่งในรูทีนเดียวกันมันทำให้รู้สึกดีเมื่อนำไปใช้กับ FORTRAN หรือโปรแกรมภาษาแอสเซมบลีที่เขียนด้วยข้อมูลทั่วโลกจำนวนมาก ด้วยวิธีการขนาดเล็กและข้อมูลในท้องถิ่นเป็นส่วนใหญ่
ฉันพบฟังก์ชั่นที่เขียนด้วยคำสั่ง Guard ง่ายต่อการติดตามมากกว่าข้อความสั่งที่ซ้อนกันยาวหนึ่งif then else
ประโยค
ในฟังก์ชั่นที่ไม่มีผลข้างเคียงไม่มีเหตุผลที่ดีที่จะมีผลตอบแทนมากกว่าครั้งเดียวและคุณควรเขียนในลักษณะการทำงาน ในวิธีที่มีผลข้างเคียงสิ่งต่าง ๆ จะเรียงตามลำดับมากขึ้น (ทำดัชนีเวลา) ดังนั้นคุณจึงเขียนในลักษณะที่จำเป็นโดยใช้คำสั่ง return เป็นคำสั่งเพื่อหยุดการดำเนินการ
กล่าวอีกนัยหนึ่งเมื่อเป็นไปได้โปรดสนับสนุนสไตล์นี้
return a > 0 ?
positively(a):
negatively(a);
มากกว่านี้
if (a > 0)
return positively(a);
else
return negatively(a);
หากคุณพบว่าคุณเขียนเงื่อนไขซ้อนหลาย ๆ ชั้นอาจเป็นไปได้ว่าคุณสามารถปรับโครงสร้างได้อีกครั้งโดยใช้รายการเพรดิเคต หากคุณพบว่าไอเอฟเอสและเอลส์ของคุณอยู่ห่างไกลจากวากยสัมพันธ์คุณอาจต้องการแบ่งมันออกเป็นฟังก์ชั่นที่เล็กลง บล็อกที่มีเงื่อนไขซึ่งมีมากกว่าข้อความหน้าจอจะอ่านยาก
ไม่มีกฎที่ยากและรวดเร็วที่ใช้กับทุกภาษา สิ่งที่ชอบมีคำสั่งคืนเดียวจะไม่ทำให้รหัสของคุณดี แต่รหัสที่ดีจะทำให้คุณสามารถเขียนฟังก์ชันของคุณได้
ฉันเคยเห็นมันในมาตรฐานการเข้ารหัสสำหรับ C ++ ที่เป็นการแฮงเอาท์จาก C ราวกับว่าคุณไม่มี RAII หรือการจัดการหน่วยความจำอัตโนมัติอื่น ๆ คุณต้องล้างข้อมูลสำหรับการส่งคืนแต่ละครั้งซึ่งหมายถึงการตัดและวาง จากการล้างข้อมูลหรือข้ามไป (เหมือนกันกับ 'ที่สุด' ในภาษาที่จัดการ) ซึ่งทั้งสองอย่างนี้ถือว่าเป็นรูปแบบที่ไม่ดี หากการปฏิบัติของคุณคือการใช้พอยน์เตอร์และคอลเล็กชั่นอัจฉริยะใน C ++ หรือระบบหน่วยความจำอัตโนมัติอื่น ๆ ก็ไม่มีเหตุผลที่ดีสำหรับมันและมันก็เป็นเรื่องเกี่ยวกับความสามารถในการอ่าน
auto_ptr
คุณสามารถใช้พอยน์เตอร์แบบขนานได้ แม้ว่ามันจะแปลกที่จะเขียนโค้ด 'ที่ได้รับการเพิ่มประสิทธิภาพ' ด้วยคอมไพเลอร์ที่ไม่ได้รับการเพิ่มประสิทธิภาพในตอนแรก
try
... finally
ใน Java) และคุณจำเป็นต้องทำการบำรุงรักษาทรัพยากรคุณสามารถทำได้เพียงครั้งเดียว กลับมาที่จุดสิ้นสุดของวิธีการ ก่อนที่คุณจะทำสิ่งนี้คุณควรพิจารณาปรับเปลี่ยนรหัสอย่างจริงจังเพื่อกำจัดสถานการณ์
ฉันเชื่อในแนวคิดที่ว่าการส่งคืนคำสั่งที่ตรงกลางของฟังก์ชันนั้นไม่ดี คุณสามารถใช้ return เพื่อสร้างส่วนคำสั่งยามสองสามที่ด้านบนของฟังก์ชั่นและแน่นอนบอกคอมไพเลอร์ว่าจะส่งคืนที่จุดสิ้นสุดของฟังก์ชันได้อย่างไรโดยไม่มีปัญหา แต่การส่งคืนในฟังก์ชันตรงกลางอาจเป็นเรื่องง่ายที่จะพลาด ทำให้ฟังก์ชั่นการตีความยากขึ้น
มีเหตุผลที่ดีหรือไม่ที่จะเป็นการดีกว่าที่จะมีคำสั่ง return หนึ่งคำในฟังก์ชัน?
ใช่มี:
คำถามนี้มักถูกโพสต์ว่าเป็นขั้วคู่ที่ผิดพลาดระหว่างผลตอบแทนหลายรายการหรือซ้อนกันอย่างลึกล้ำถ้ามีคำสั่ง มีวิธีแก้ปัญหาที่สามเกือบทุกครั้งซึ่งเป็นแบบเส้นตรง (ไม่มีการซ้อนแบบลึก) โดยมีเพียงจุดทางออกเดียว
อัปเดต : แนวทาง MISRAเห็นได้ชัดว่าส่งเสริมการออกเพียงครั้งเดียวเช่นกัน
เพื่อความชัดเจนฉันไม่ได้บอกว่ามันผิดเสมอไปที่จะได้รับผลตอบแทนหลายครั้ง แต่ด้วยวิธีการแก้ปัญหาที่เทียบเท่าเป็นอย่างอื่นมีเหตุผลที่ดีมากมายที่จะชอบโซลูชันที่ให้ผลตอบแทนเพียงครั้งเดียว
Contract.Ensures
กับจุดส่งคืนหลายจุดได้
goto
เพื่อรับรหัสการล้างข้อมูลทั่วไปคุณอาจทำให้ฟังก์ชั่นนั้นง่ายขึ้นเพื่อให้มีรหัสreturn
ที่ท้ายรหัสการล้างข้อมูล ดังนั้นคุณอาจจะบอกว่าคุณได้แก้ไขปัญหาด้วยแต่ผมว่าคุณแก้ไขได้โดยง่ายที่จะเป็นหนึ่งเดียวgoto
return
การมีจุดออกเพียงจุดเดียวจะให้ประโยชน์ในการดีบักเนื่องจากช่วยให้คุณสามารถตั้งค่าเบรกพอยต์เดียวที่ท้ายฟังก์ชั่นเพื่อดูว่าจะส่งคืนค่าใด
โดยทั่วไปฉันพยายามที่จะมีเพียงจุดทางออกเดียวจากฟังก์ชั่น อย่างไรก็ตามมีบางครั้งที่การทำเช่นนั้นจริง ๆ แล้วจบลงด้วยการสร้างฟังก์ชันของฟังก์ชันที่ซับซ้อนเกินความจำเป็นซึ่งในกรณีนี้จะดีกว่าถ้ามีจุดออกหลายจุด มันจะต้องเป็น "การเรียกการตัดสิน" ตามความซับซ้อนที่เกิดขึ้น แต่เป้าหมายควรมีจุดออกน้อยที่สุดเท่าที่จะทำได้โดยไม่ต้องเสียสละความซับซ้อนและความเข้าใจ
ไม่เพราะเราไม่ได้อยู่ในทศวรรษ 1970 อีกต่อไป หากฟังก์ชันของคุณยาวพอที่การคืนค่าหลายครั้งจะเป็นปัญหาแสดงว่ามันยาวเกินไป
(ค่อนข้างนอกเหนือจากข้อเท็จจริงที่ว่าฟังก์ชั่นหลายสายในภาษาที่มีข้อยกเว้นจะมีจุดออกหลายจุดอยู่แล้ว)
การตั้งค่าของฉันจะเป็นทางออกเดียวเว้นแต่มันจะซับซ้อนมาก ฉันได้พบว่าในบางกรณีมีจุดที่มีอยู่หลายจุดสามารถปกปิดปัญหาการออกแบบที่สำคัญอื่น ๆ ได้:
public void DoStuff(Foo foo)
{
if (foo == null) return;
}
เมื่อเห็นรหัสนี้ฉันจะถามทันที:
อาจขึ้นอยู่กับคำตอบของคำถามเหล่านี้
ในทั้งสองกรณีข้างต้นรหัสอาจถูกทำใหม่ด้วยการยืนยันเพื่อให้แน่ใจว่า 'foo' ไม่เคยเป็นโมฆะและผู้โทรที่เกี่ยวข้องเปลี่ยนไป
มีสองเหตุผลอื่น ๆ (โดยเฉพาะฉันคิดว่ารหัส C ++) ที่มีอยู่หลายรายการจริงอาจมีผลกระทบเชิงลบ ขนาดรหัสและการเพิ่มประสิทธิภาพของคอมไพเลอร์
วัตถุที่ไม่ใช่ POD C ++ ในขอบเขตที่ออกจากฟังก์ชันจะมี destructor ที่เรียกว่า ในกรณีที่มีข้อความสั่งคืนหลายรายการอาจเป็นกรณีที่มีวัตถุต่าง ๆ อยู่ในขอบเขตดังนั้นรายการของ destructors ที่จะเรียกจะแตกต่างกัน คอมไพเลอร์จำเป็นต้องสร้างรหัสสำหรับแต่ละคำสั่งการส่งคืน:
void foo (int i, int j) {
A a;
if (i > 0) {
B b;
return ; // Call dtor for 'b' followed by 'a'
}
if (i == j) {
C c;
B b;
return ; // Call dtor for 'b', 'c' and then 'a'
}
return 'a' // Call dtor for 'a'
}
หากขนาดรหัสเป็นปัญหา - นี่อาจเป็นสิ่งที่ควรหลีกเลี่ยง
ปัญหาอื่น ๆ เกี่ยวข้องกับ "การเพิ่มประสิทธิภาพการคืนค่าชื่อ" (aka Copy Elision, ISO C ++ '03 12.8 / 15) C ++ อนุญาตให้มีการนำไปใช้ข้ามการเรียกตัวสร้างสำเนาหากสามารถ:
A foo () {
A a1;
// do something
return a1;
}
void bar () {
A a2 ( foo() );
}
เพียงแค่ใช้รหัสตามที่เป็นอยู่วัตถุ 'a1' จะถูกสร้างใน 'foo' จากนั้นโครงสร้างการคัดลอกจะถูกเรียกเพื่อสร้าง 'a2' อย่างไรก็ตามการคัดลอกการอนุญาตให้คอมไพเลอร์สร้าง 'a1' ในตำแหน่งเดียวกันบนสแต็กเป็น 'a2' ดังนั้นจึงไม่จำเป็นต้อง "คัดลอก" วัตถุเมื่อฟังก์ชั่นกลับมา
จุดทางออกหลายจุดทำให้การทำงานของคอมไพเลอร์ยุ่งยากในการตรวจจับสิ่งนี้และอย่างน้อยสำหรับเวอร์ชันล่าสุดของ VC ++ การปรับให้เหมาะสมไม่ได้เกิดขึ้นที่ส่วนของฟังก์ชั่นมีผลตอบแทนหลายตัว ดูการเพิ่มประสิทธิภาพค่าที่ส่งคืนของชื่อใน Visual C ++ 2005สำหรับรายละเอียดเพิ่มเติม
throw new ArgumentNullException()
ใน C # ในกรณีนี้) ฉันชอบข้อพิจารณาอื่น ๆ ของคุณพวกเขาทั้งหมดใช้ได้กับฉันและอาจมีความสำคัญในบางอย่าง บริบทเฉพาะ
foo
ถูกทดสอบไม่มีอะไรเกี่ยวข้องกับเรื่องซึ่งจะทำif (foo == NULL) return; dowork;
หรือif (foo != NULL) { dowork; }
การมีจุดทางออกเดียวจะช่วยลดความซับซ้อนของ Cyclomaticดังนั้นในทางทฤษฎีแล้วลดความน่าจะเป็นที่คุณจะแนะนำบั๊กในโค้ดของคุณเมื่อคุณเปลี่ยน อย่างไรก็ตามการฝึกซ้อมมีแนวโน้มที่จะแนะนำว่าจำเป็นต้องมีวิธีการปฏิบัติเพิ่มเติม ฉันมักจะตั้งเป้าหมายที่จะออกจากจุดเดียว แต่ให้รหัสของฉันมีหลายจุดหากอ่านได้มากกว่า
ฉันบังคับให้ฉันใช้return
คำสั่งเพียงคำเดียวเพราะมันจะสร้างกลิ่นรหัสได้ ให้ฉันอธิบาย:
function isCorrect($param1, $param2, $param3) {
$toret = false;
if ($param1 != $param2) {
if ($param1 == ($param3 * 2)) {
if ($param2 == ($param3 / 3)) {
$toret = true;
} else {
$error = 'Error 3';
}
} else {
$error = 'Error 2';
}
} else {
$error = 'Error 1';
}
return $toret;
}
(เงื่อนไขเป็นสิทธิ์ ... )
ยิ่งมีเงื่อนไขมากเท่าไหร่ฟังก์ชั่นก็จะยิ่งใหญ่และยากต่อการอ่าน ดังนั้นหากคุณปรับตัวกับกลิ่นรหัสคุณจะเข้าใจได้และต้องการปรับเปลี่ยนรหัสอีกครั้ง สองวิธีที่เป็นไปได้คือ:
ผลตอบแทนหลายรายการ
function isCorrect($param1, $param2, $param3) {
if ($param1 == $param2) { $error = 'Error 1'; return false; }
if ($param1 != ($param3 * 2)) { $error = 'Error 2'; return false; }
if ($param2 != ($param3 / 3)) { $error = 'Error 3'; return false; }
return true;
}
ฟังก์ชั่นแยก
function isEqual($param1, $param2) {
return $param1 == $param2;
}
function isDouble($param1, $param2) {
return $param1 == ($param2 * 2);
}
function isThird($param1, $param2) {
return $param1 == ($param2 / 3);
}
function isCorrect($param1, $param2, $param3) {
return !isEqual($param1, $param2)
&& isDouble($param1, $param3)
&& isThird($param2, $param3);
}
จริงอยู่ที่มันมีความยาวและค่อนข้างยุ่ง แต่ในกระบวนการของการปรับฟังก์ชั่นใหม่ด้วยวิธีนี้เราได้
ฉันจะบอกว่าคุณควรมีจำนวนมากตามที่ต้องการหรือสิ่งใดก็ตามที่ทำให้โค้ดสะอาด (เช่นคำสั่งป้องกัน )
ฉันไม่เคยได้ยิน / เห็น "แนวปฏิบัติที่ดีที่สุด" ใด ๆ เป็นการส่วนตัวว่าคุณควรมีข้อความสั่งการส่งคืนเดียว
ส่วนใหญ่ฉันมักจะออกจากฟังก์ชันโดยเร็วที่สุดตามเส้นทางตรรกะ (ส่วนคำสั่งป้องกันเป็นตัวอย่างที่ยอดเยี่ยมของเรื่องนี้)
ฉันเชื่อว่าผลตอบแทนหลายรายการมักจะดี (ในรหัสที่ฉันเขียนใน C #) รูปแบบการส่งคืนเป็นแบบโฮลด์จาก C แต่คุณอาจไม่ได้เข้ารหัสใน C
ไม่มีกฎหมายกำหนดให้เพียงหนึ่งจุดออกหาวิธีการในทุกภาษาการเขียนโปรแกรม บางคนยืนยันในความเหนือกว่าของสไตล์นี้และบางครั้งพวกเขายกระดับเป็น "กฎ" หรือ "กฎหมาย" แต่ความเชื่อนี้ไม่ได้รับการสนับสนุนจากหลักฐานหรือการวิจัยใด ๆ
มากกว่าหนึ่งสไตล์การส่งคืนอาจเป็นนิสัยที่ไม่ดีในรหัส C ซึ่งทรัพยากรจะต้องได้รับการจัดสรรอย่างชัดเจน แต่ภาษาเช่น Java, C #, Python หรือ JavaScript ที่มีโครงสร้างเช่นการรวบรวมขยะอัตโนมัติและtry..finally
บล็อก (และusing
บล็อกใน C # ) และอาร์กิวเมนต์นี้ใช้ไม่ได้ - ในภาษาเหล่านี้เป็นเรื่องแปลกมากที่ต้องมีการจัดสรรทรัพยากรด้วยตนเองแบบรวมศูนย์
มีหลายกรณีที่การคืนค่าครั้งเดียวสามารถอ่านได้มากกว่าและกรณีที่ไม่มี ดูว่ามันช่วยลดจำนวนบรรทัดของรหัสทำให้ตรรกะชัดเจนขึ้นหรือลดจำนวนการจัดฟันและเยื้องหรือตัวแปรชั่วคราว
ดังนั้นให้ใช้ผลตอบแทนมากที่สุดเท่าที่เหมาะสมกับความรู้สึกอ่อนไหวของคุณเพราะเป็นปัญหาเรื่องเลย์เอาต์และการอ่านไม่ใช่เรื่องเทคนิค
ผมได้พูดคุยเกี่ยวกับเรื่องนี้ที่มีความยาวมากขึ้นในบล็อกของฉัน
มีสิ่งที่ดีที่จะพูดเกี่ยวกับการมีจุดทางออกเดียวเช่นเดียวกับที่มีสิ่งเลวร้ายที่จะพูดเกี่ยวกับการเขียนโปรแกรม"ลูกศร" ที่หลีกเลี่ยงไม่ได้ที่เป็นผล
หากใช้จุดออกหลายจุดระหว่างการตรวจสอบความถูกต้องของอินพุตหรือการจัดสรรทรัพยากรฉันพยายามใส่ 'ข้อผิดพลาดออก' ทั้งหมดอย่างชัดเจนที่ด้านบนของฟังก์ชั่น
ทั้งบทความการเขียนโปรแกรมสปาร์ตันของ "SSDSLPedia" และฟังก์ชั่นบทความจุดทางออกเดียวของ "Wiki รูปแบบที่เก็บพอร์ตแลนด์" มีข้อโต้แย้งที่ลึกซึ้งเกี่ยวกับเรื่องนี้ นอกจากนี้แน่นอนยังมีโพสต์นี้ที่จะต้องพิจารณา
หากคุณต้องการจุดทางออกเดียว (ในภาษาที่ไม่มีข้อยกเว้น) ตัวอย่างเช่นเพื่อเผยแพร่ทรัพยากรในที่เดียวฉันพบว่าแอพพลิเคชั่นของ goto นั้นดี ดูตัวอย่างนี้เป็นตัวอย่างที่วางแผนไว้ (บีบอัดเพื่อบันทึกหน้าจออสังหาริมทรัพย์):
int f(int y) {
int value = -1;
void *data = NULL;
if (y < 0)
goto clean;
if ((data = malloc(123)) == NULL)
goto clean;
/* More code */
value = 1;
clean:
free(data);
return value;
}
โดยส่วนตัวแล้วโดยทั่วไปแล้วฉันไม่ชอบการเขียนโปรแกรมลูกศรมากกว่าที่ฉันไม่ชอบจุดทางออกหลายจุดแม้ว่าทั้งสองจะมีประโยชน์เมื่อใช้อย่างถูกต้อง ที่ดีที่สุดของหลักสูตรคือการจัดโครงสร้างโปรแกรมของคุณเพื่อไม่ต้องการ การแบ่งหน้าที่ของคุณออกเป็นหลาย ๆ ชิ้นมักจะช่วย :)
แม้ว่าเมื่อทำเช่นนั้นฉันพบว่าฉันจบลงด้วยการออกหลายจุดอย่างไรก็ตามในตัวอย่างนี้ที่มีฟังก์ชั่นขนาดใหญ่บางส่วนถูกแบ่งออกเป็นฟังก์ชั่นขนาดเล็กหลาย:
int g(int y) {
value = 0;
if ((value = g0(y, value)) == -1)
return -1;
if ((value = g1(y, value)) == -1)
return -1;
return g2(y, value);
}
ขึ้นอยู่กับโครงการหรือแนวทางการเข้ารหัสรหัสส่วนใหญ่ของแผ่นบอยเลอร์สามารถถูกแทนที่ด้วยมาโคร ในฐานะที่เป็นโน้ตด้านข้างการทำลายมันด้วยวิธีนี้ทำให้ฟังก์ชั่น g0, g1, g2 นั้นง่ายมากในการทดสอบทีละอย่าง
เห็นได้ชัดว่าใน OO และภาษาที่เปิดใช้งานข้อยกเว้นฉันจะไม่ใช้ if-statement เช่นนั้น (หรือเลยถ้าฉันสามารถหลบมันได้ด้วยความพยายามเพียงเล็กน้อย) และรหัสจะชัดเจนยิ่งขึ้น และไม่ใช่ลูกศร และผลตอบแทนที่ไม่ใช่ขั้นสุดท้ายส่วนใหญ่อาจเป็นข้อยกเว้น
ในระยะสั้น;
คุณจะรู้ว่าสุภาษิต - ความงามในสายตาของคนดู
บางคนสาบานNetBeansและบางส่วนจากความคิด IntelliJบางโดยงูหลามและบางส่วนโดยPHP
ในร้านค้าบางแห่งคุณอาจตกงานถ้าคุณยืนยันที่จะทำสิ่งนี้:
public void hello()
{
if (....)
{
....
}
}
คำถามคือทั้งหมดที่เกี่ยวกับการมองเห็นและการบำรุงรักษา
ฉันติดการใช้พีชคณิตแบบบูลเพื่อลดและลดความซับซ้อนของตรรกะและการใช้เครื่องรัฐ อย่างไรก็ตามมีเพื่อนร่วมงานในอดีตที่เชื่อว่าการจ้างงานของฉัน "เทคนิคทางคณิตศาสตร์" ในการเขียนโปรแกรมไม่เหมาะสมเพราะมันจะไม่สามารถมองเห็นและบำรุงรักษาได้ และนั่นก็เป็นการฝึกฝนที่ไม่ดี คนที่เสียใจเทคนิคที่ฉันใช้นั้นสามารถมองเห็นได้และรักษาได้ - เพราะเมื่อฉันกลับไปที่รหัสหกเดือนต่อมาฉันจะเข้าใจรหัสอย่างชัดเจนแทนที่จะเห็นระเบียบปาเก็ตตี้ที่เลื่องลือ
เฮ้บัดดี้ (เหมือนอดีตลูกค้าเคยพูด) ทำสิ่งที่คุณต้องการตราบใดที่คุณรู้วิธีแก้ไขเมื่อฉันต้องการให้คุณแก้ไข
ฉันจำได้เมื่อ 20 ปีที่แล้วเพื่อนร่วมงานของฉันถูกไล่ออกเพราะจ้างสิ่งที่วันนี้จะเรียกว่ากลยุทธ์การพัฒนาที่คล่องตัว เขามีแผนเพิ่มขึ้นอย่างพิถีพิถัน แต่ผู้จัดการของเขาตะโกนใส่เขา "คุณไม่สามารถเพิ่มฟีเจอร์ให้กับผู้ใช้ได้! คุณต้องติดอยู่กับน้ำตก " การตอบสนองของเขาต่อผู้จัดการคือการพัฒนาที่เพิ่มขึ้นจะแม่นยำยิ่งขึ้นตามความต้องการของลูกค้า เขาเชื่อในการพัฒนาสำหรับความต้องการของลูกค้า แต่ผู้จัดการเชื่อในการเขียนโค้ดถึง "ความต้องการของลูกค้า"
เรามักจะมีความผิดในการทำลายการทำข้อมูลให้เป็นมาตรฐาน, ขอบเขตMVPและMVC เราอินไลน์แทนที่จะสร้างฟังก์ชั่น เราใช้ทางลัด
ส่วนตัวผมเชื่อว่า PHP นั้นเป็นสิ่งที่ไม่ดี แต่ฉันรู้อะไร ข้อโต้แย้งเชิงทฤษฎีทั้งหมดเกิดขึ้นเพื่อพยายามทำตามกฎหนึ่งชุด
คุณภาพ = ความแม่นยำการบำรุงรักษาและผลกำไร
กฎอื่น ๆ ทั้งหมดหายไปเป็นพื้นหลัง และแน่นอนกฎนี้ไม่เคยจางหายไป:
ความเกียจคร้านเป็นคุณธรรมของโปรแกรมเมอร์ที่ดี
ฉันโน้มตัวไปข้างหน้าโดยใช้คำสั่งเพื่อกลับก่อนและออกจากที่ส่วนท้ายของวิธีการ กฎการเข้าและออกครั้งเดียวมีความสำคัญทางประวัติศาสตร์และเป็นประโยชน์อย่างยิ่งเมื่อต้องจัดการกับรหัสดั้งเดิมที่วิ่งไปถึง 10 หน้า A4 สำหรับวิธี C ++ เดียวที่มีการส่งคืนหลายครั้ง (และข้อบกพร่องมากมาย) เมื่อเร็ว ๆ นี้การปฏิบัติที่ดีที่ได้รับการยอมรับคือการทำให้วิธีการเล็ก ๆ ซึ่งทำให้หลายทางออกน้อยกว่าความต้านทานต่อความเข้าใจ ในตัวอย่าง Kronoz ต่อไปนี้คัดลอกมาจากด้านบนคำถามคือเกิดอะไรขึ้นใน// ส่วนที่เหลือของรหัส ... ?:
void string fooBar(string s, int? i) {
if(string.IsNullOrEmpty(s) || i == null) return null;
var res = someFunction(s, i);
foreach(var r in res) {
if(!r.Passed) return null;
}
// Rest of code...
return ret;
}
ฉันรู้ว่าตัวอย่างนั้นถูกประดิษฐ์ขึ้นมาบ้าง แต่ฉันจะถูกล่อลวงให้ปรับโครงสร้างลูปforeachลงในคำสั่ง LINQ ซึ่งอาจถือได้ว่าเป็นประโยคป้องกัน อีกครั้งในตัวอย่าง contrived เจตนาของรหัสไม่ชัดเจนและsomeFunction ()อาจมีผลข้างเคียงบางอย่างอื่น ๆ หรือผลที่อาจจะใช้ใน// ส่วนที่เหลือของรหัส ...
if (string.IsNullOrEmpty(s) || i == null) return null;
if (someFunction(s, i).Any(r => !r.Passed)) return null;
ให้ฟังก์ชัน refactored ต่อไปนี้:
void string fooBar(string s, int? i) {
if (string.IsNullOrEmpty(s) || i == null) return null;
if (someFunction(s, i).Any(r => !r.Passed)) return null;
// Rest of code...
return ret;
}
null
แทนที่จะทิ้งข้อยกเว้นที่ระบุว่าการโต้เถียงนั้นไม่ได้รับการยอมรับ?
เหตุผลหนึ่งที่ฉันคิดได้ก็คือการบำรุงรักษาโค้ด: คุณมีทางออกเดียว หากคุณต้องการเปลี่ยนรูปแบบของผลลัพธ์ ... มันง่ายกว่าที่จะนำมาใช้ นอกจากนี้สำหรับการแก้ไขข้อบกพร่องคุณสามารถเพียงเบรกพอยต์ที่นั่น :)
ต้องบอกว่าฉันเคยทำงานในห้องสมุดที่กำหนดมาตรฐานการเข้ารหัส 'หนึ่งคืนคำสั่งต่อฟังก์ชั่น' และฉันพบว่ามันค่อนข้างยาก ฉันเขียนโค้ดคำนวณตัวเลขจำนวนมากและมักจะมี 'กรณีพิเศษ' ดังนั้นโค้ดจึงค่อนข้างยากที่จะติดตาม ...
จุดทางออกหลายจุดเป็นสิ่งที่ดีสำหรับฟังก์ชั่นที่มีขนาดเล็กนั่นคือฟังก์ชั่นที่สามารถดูได้บนความยาวหน้าจอเดียวโดยสมบูรณ์ หากฟังก์ชั่นที่มีความยาวนั้นมีจุดออกหลายจุดเช่นกันมันเป็นสัญญาณว่าฟังก์ชันสามารถสับเพิ่มขึ้นได้
ที่กล่าวว่าฉันหลีกเลี่ยงฟังก์ชั่นหลายทางออกเว้นแต่จำเป็นจริงๆ ฉันรู้สึกถึงความเจ็บปวดของแมลงที่เกิดจากการกลับมาของสัตว์จรจัดในแนวที่ไม่ชัดเจนในการทำงานที่ซับซ้อนมากขึ้น
ฉันได้ทำงานกับมาตรฐานการเข้ารหัสที่แย่มากซึ่งบังคับให้ทางออกทางออกเดียวกับคุณและผลลัพธ์ก็คือสปาเก็ตตี้ที่ไม่มีโครงสร้างเกือบทุกครั้งถ้าฟังก์ชั่นนั้นมีอะไรมากมายนอกจากเรื่องเล็กน้อยคุณจบลงด้วยการหยุดพักมากมาย
if
คำสั่งในด้านหน้าของแต่ละวิธีการโทรที่กลับมาประสบความสำเร็จหรือไม่ :(
จุดทางออกเดียว - สิ่งอื่น ๆ ที่เท่าเทียมกัน - ทำให้โค้ดอ่านง่ายขึ้น แต่มีการจับ: การก่อสร้างที่เป็นที่นิยม
resulttype res;
if if if...
return res;
เป็นของปลอม "res =" นั้นไม่ดีไปกว่า "คืน" มันมีคำสั่งคืนเดียว แต่หลายจุดที่ฟังก์ชั่นจริงสิ้นสุด
หากคุณมีฟังก์ชั่นที่มีผลตอบแทนหลายรายการ (หรือ "res =" s) ก็มักจะเป็นความคิดที่ดีที่จะแบ่งออกเป็นฟังก์ชั่นเล็ก ๆ หลาย ๆ ที่มีจุดทางออกเดียว
นโยบายตามปกติของฉันคือมีคำสั่งส่งคืนเดียวที่ท้ายฟังก์ชั่นเว้นแต่ว่าความซับซ้อนของรหัสจะลดลงอย่างมากโดยการเพิ่มมากขึ้น ในความเป็นจริงฉันเป็นแฟนของไอเฟลซึ่งบังคับใช้กฎการส่งคืนเดียวโดยไม่มีคำสั่งส่งคืน (มีเพียงตัวแปร 'ผลลัพธ์' ที่สร้างขึ้นโดยอัตโนมัติเพื่อใส่ผลลัพธ์ของคุณ)
มีกรณีที่รหัสสามารถทำให้ชัดเจนกับผลตอบแทนหลายกว่ารุ่นที่ชัดเจนโดยไม่ต้องพวกเขาจะเป็น อาจมีการโต้แย้งว่าจำเป็นต้องมีการทำใหม่หากคุณมีฟังก์ชั่นที่ซับซ้อนเกินกว่าที่จะเข้าใจได้โดยไม่ต้องมีคำสั่งการส่งคืนหลายครั้ง
หากคุณจบด้วยการส่งคืนมากกว่าสองสามครั้งอาจมีบางอย่างผิดปกติกับรหัสของคุณ มิฉะนั้นฉันจะยอมรับว่าบางครั้งมันก็ดีที่ได้กลับมาจากหลาย ๆ ที่ในรูทีนย่อยโดยเฉพาะเมื่อมันทำให้โค้ดสะอาดขึ้น
sub Int_to_String( Int i ){
given( i ){
when 0 { return "zero" }
when 1 { return "one" }
when 2 { return "two" }
when 3 { return "three" }
when 4 { return "four" }
...
default { return undef }
}
}
คงจะเขียนได้ดีกว่านี้
@Int_to_String = qw{
zero
one
two
three
four
...
}
sub Int_to_String( Int i ){
return undef if i < 0;
return undef unless i < @Int_to_String.length;
return @Int_to_String[i]
}
หมายเหตุนี่เป็นเพียงตัวอย่างด่วน
ฉันโหวตให้ผลตอบแทนเดี่ยวในตอนท้ายเพื่อเป็นแนวทาง นี่จะช่วยจัดการรหัสทั่วไปให้ล้างข้อมูล ... ตัวอย่างเช่นลองดูรหัสต่อไปนี้ ...
void ProcessMyFile (char *szFileName)
{
FILE *fp = NULL;
char *pbyBuffer = NULL:
do {
fp = fopen (szFileName, "r");
if (NULL == fp) {
break;
}
pbyBuffer = malloc (__SOME__SIZE___);
if (NULL == pbyBuffer) {
break;
}
/*** Do some processing with file ***/
} while (0);
if (pbyBuffer) {
free (pbyBuffer);
}
if (fp) {
fclose (fp);
}
}
นี่อาจเป็นมุมมองที่ผิดปกติ แต่ฉันคิดว่าทุกคนที่เชื่อว่าจะต้องมีการส่งคืนงบหลายรายการโดยไม่ต้องใช้ดีบักเกอร์บนไมโครโปรเซสเซอร์ที่รองรับจุดพักฮาร์ดแวร์เพียง 4 แห่ง ;-)
ในขณะที่ปัญหาของ "รหัสลูกศร" ถูกต้องสมบูรณ์ปัญหาหนึ่งที่ดูเหมือนว่าจะหายไปเมื่อใช้คำสั่งส่งคืนหลายอยู่ในสถานการณ์ที่คุณกำลังใช้ดีบักเกอร์ คุณไม่มีตำแหน่ง catch-all ที่สะดวกเพื่อวางเบรกพอยต์เพื่อรับประกันว่าคุณจะเห็นทางออกและด้วยเหตุนี้สภาพการส่งคืน
ยิ่งคุณส่งคืนงบมากขึ้นในฟังก์ชั่นความซับซ้อนที่สูงขึ้นในวิธีใดวิธีหนึ่ง หากคุณพบว่าตัวเองสงสัยว่าคุณมีข้อความสั่งคืนมากเกินไปคุณอาจต้องถามตัวเองว่ามีรหัสบรรทัดมากเกินไปในฟังก์ชันนั้นหรือไม่
แต่ไม่มีอะไรผิดปกติกับหนึ่ง / หลายข้อความกลับ ในบางภาษามันเป็นวิธีปฏิบัติที่ดีกว่า (C ++) มากกว่าในภาษาอื่น (C)