“ วิธีการที่ไม่ดี” เป็นรหัสที่ไม่เกี่ยวข้องในการลองจับในที่สุดบล็อก?


11

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

ใน Q อ้างอิงแล้วรหัสสุดท้ายเกี่ยวข้องกับโครงสร้างที่ใช้และความจำเป็นในการดึงข้อมูลล่วงหน้า คำถามของฉันแตกต่างออกไปเล็กน้อยและฉันเชื่อว่ามันเป็นสิ่งที่ดีสำหรับผู้ชมที่กว้างขึ้น ตัวอย่างเฉพาะของฉันคือแอป C # winform แต่สิ่งนี้จะใช้กับ C ++ / Java ในที่สุดการใช้งานเช่นกัน

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

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

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

ประมาณ:

    methodName (... )
    {
        ลอง
        {
            // มีโค้ดสำหรับวิธีการมากมาย ...
            // รหัสที่สามารถส่ง ...
            // มีโค้ดมากขึ้นสำหรับวิธีการและการส่งคืน ...
        }
        จับ (บางสิ่ง)
        {// จัดการข้อยกเว้น}
        ในที่สุด
        {
            // การล้างข้อมูลบางอย่างเกิดจากข้อยกเว้นปิดสิ่งต่างๆ
            // โค้ดเพิ่มเติมสำหรับสิ่งที่สร้างขึ้น (ไม่สนใจว่ามีข้อยกเว้นใด ๆ ที่ส่งออกไป) ...
            // อาจสร้างวัตถุเพิ่มเติม
        }
    }

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

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


"C ++" แท็กไม่ได้อยู่ที่นี่เป็น C ++ ไม่ได้ finally(และไม่จำเป็นต้อง) การใช้งานที่ดีทั้งหมดได้รับการคุ้มครองภายใต้ RAII / RRID / SBRM (ตัวย่อที่คุณต้องการ)
David Thornley

2
@DavidThornley: นอกเหนือจากตัวอย่างที่ใช้คำหลัก 'สุดท้าย' แล้วคำถามที่เหลือใช้กับ C ++ ได้อย่างสมบูรณ์แบบ ฉันเป็นนักพัฒนา C ++ และคำหลักนั้นไม่ได้ทำให้ฉันสับสนจริงๆ และเมื่อพิจารณาว่าฉันมีการสนทนาที่คล้ายกันกับทีมของฉันในแบบกึ่งปกติคำถามนี้มีความเกี่ยวข้องอย่างมากกับสิ่งที่เราทำกับ C ++
DXM

@DXM: ฉันมีปัญหาในการแสดงภาพนี้ (และฉันรู้ว่าสิ่งที่finallyทำใน C #) เทียบเท่า C ++ คืออะไร? รหัสหลังจากสิ่งที่ผมคิดว่าการเป็นcatchและที่ใช้เหมือนกันสำหรับรหัสหลังจาก C finally#
David Thornley

@DavidThornley: ในที่สุดรหัสก็คือรหัสที่รันไม่ว่าจะเกิดอะไรขึ้นก็ตาม
orlp

1
@ nightcracker นั่นไม่ถูกต้องจริงๆ มันจะไม่ถูกดำเนินการถ้าคุณทำEnvironment.FailFast(); มันอาจไม่ถูกดำเนินการหากคุณมีข้อยกเว้นที่ไม่ได้ตรวจสอบ และมันจะซับซ้อนยิ่งขึ้นถ้าคุณมีบล็อกตัววนซ้ำโดยfinallyที่คุณวนซ้ำด้วยตนเอง
svick

คำตอบ:


10

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

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

ที่ถูกกล่าวว่า ...

คำแนะนำแรกของฉันคือ:ถ้ามันยังไม่พังอย่าแตะต้องมัน!

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

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

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

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

คำแนะนำที่สามของฉันคือคุณจะถูกไฟไหม้ถ้าคุณทำผิดรหัสไม่สำคัญว่ามันเป็นความผิดของคุณหรือไม่

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

Joel อธิบายบทความของเขาได้ดีมากด้วยเหตุผลหลายประการว่าทำไมคุณไม่ควรเขียนรหัสเดิม

http://www.joelonsoftware.com/articles/fog0000000069.html

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

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

สิ่งที่เกิดขึ้นคือรหัสนั้นแย่มากจนคุณลักษณะใหม่ ๆ นั้นเป็นไปไม่ได้ที่จะนำไปใช้ แอปพลิเคชันที่มีอยู่มีปัญหาด้านประสิทธิภาพและการใช้งานอย่างจริงจัง ในที่สุดฉันถูกขอให้ทำการเปลี่ยนแปลงที่จะใช้เวลาฉัน 3-4 เดือน (ส่วนใหญ่เป็นเพราะการทำงานกับรหัสนั้นใช้เวลามากขึ้นกว่าปกติ) ฉันคิดว่าฉันสามารถเขียนใหม่ทั้งชิ้น (รวมถึงการใช้คุณสมบัติใหม่ที่ต้องการ) ในเวลาประมาณ 5-6 เดือน ฉันนำข้อเสนอนี้ไปให้ผู้มีส่วนได้เสียและพวกเขาตกลงที่จะเขียนใหม่ (โชคดีสำหรับฉัน)

หลังจากที่ฉันเขียนชิ้นนี้พวกเขาเข้าใจฉันสามารถส่งมอบสิ่งที่ดีกว่าสิ่งที่พวกเขามีอยู่แล้ว ดังนั้นฉันจึงสามารถเขียนแอปพลิเคชันใหม่ทั้งหมดได้

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

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

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


1
+1 แต่ประโยคสุดท้ายหมายถึงฆาตกรต่อเนื่อง มันจำเป็นจริงๆเหรอ?
MarkJ

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

@RickyClarkson ฉันเห็นด้วย มันยากมากที่จะรู้ว่าเมื่อคุณทำมากเกินไป (ไม่จำเป็นต้องทำการเปลี่ยนใหม่จริง ๆ ) และเมื่อคุณทำร้ายแอปพลิเคชันด้วยการเพิ่มการเปลี่ยนแปลง
อเล็กซ์

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

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

2

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

http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052

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

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


2

สมมติว่าคุณมีวิธีการโทรหกวิธีซึ่งสี่วิธีควรจะเรียกก็ต่อเมื่อวิธีแรกเสร็จสมบูรณ์โดยไม่มีข้อผิดพลาด พิจารณาทางเลือกสองทาง:

try {
     method1();  // throws
     method2();
     method3();
     method4();
     method5();
 } catch(e) {
     // handle error
 }
 method6();

VS

 try {
      method1();  // throws
 }
 catch(e) {
      // handle error
 }
 if(! error ) {
     method2();
     method3();
     method4();
     method5();
 }
 method6();

ในรหัสที่สองคุณกำลังปฏิบัติต่อข้อยกเว้นเช่นรหัสส่งคืน ตามหลักการแล้วสิ่งนี้เหมือนกับ:

rc = method1();
if( rc != error ) {
     method2();
     method3();
     method4();
     method5();
 }
 method6();

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

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

rc = method1();
if( rc != error ) {
     rc = method2();
     if( rc != error ) {
         rc = method3();
         if( rc != error ) {
             rc = method4();
             if(rc != error ) {
                 method5();
             }
         }
     }
 }
 method6();

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

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

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

void method0() : throws MyNewException 
{
    try {
        method1();  // throws MyOtherException
    }
    catch(e) {
        if(e == MyOtherException)
            throw MyNewException();
    }
    method2();
}

ที่นี่คุณเพิ่งเพิ่มเลเยอร์และเลเยอร์เพิ่มความสับสน แค่ทำสิ่งนี้:

void method0() : throws MyOtherException
{
    method1();
    method2();
}

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

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


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

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

... สถานะของระบบจะตรงกับความคาดหวังของรหัส หากข้อยกเว้นที่ถูกส่งออกมาจากวิธีที่ 1 ควรได้รับการจัดการที่แตกต่างจากที่ถูกโยนโดยวิธีที่ 2 วิธีที่สามารถทำได้อย่างมีเหตุผลโดยไม่ต้องห่อพวกเขา?
supercat

โดยหลักการแล้วข้อยกเว้นของคุณนั้นละเอียดพอที่จะเป็นสถานการณ์ที่หายาก
Gort the Robot
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.