ฉันควรบันทึกข้อผิดพลาดเกี่ยวกับข้อยกเว้นการสร้างคอนสตรัคเตอร์หรือไม่


15

ฉันกำลังสร้างแอปพลิเคชันอยู่สองสามเดือนและฉันก็ตระหนักถึงรูปแบบที่ปรากฏ:

logger.error(ERROR_MSG);
throw new Exception(ERROR_MSG);

หรือเมื่อจับ:

try { 
    // ...block that can throw something
} catch (Exception e) {
    logger.error(ERROR_MSG, e);
    throw new MyException(ERROR_MSG, e);
}

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

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

ดังนั้นนี่เป็นรูปแบบการต่อต้านหรือไม่ ถ้าเป็นเช่นนั้นทำไม


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

คำตอบ:


18

ไม่แน่ใจว่ามันมีคุณสมบัติเป็นรูปแบบต่อต้าน แต่ IMO เป็นความคิดที่ไม่ดี: มันเป็นข้อต่อที่ไม่จำเป็นในการรวมข้อยกเว้นกับการบันทึก

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

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

สุดท้ายจะเกิดอะไรขึ้นถ้ามีข้อยกเว้นเกิดขึ้นระหว่างการบันทึกข้อยกเว้นอื่น? คุณจะเข้าสู่ระบบที่? มันยุ่งเหยิง ...

ทางเลือกของคุณคือ:

  • จับบันทึกและ (อีกครั้ง) โยนตามที่คุณให้ไว้ในตัวอย่างของคุณ
  • สร้างคลาส ExceptionHelper เพื่อทำทั้งสองอย่างสำหรับคุณ แต่ผู้ช่วยมีกลิ่นรหัสและฉันจะไม่แนะนำสิ่งนี้
  • ย้ายการจัดการข้อยกเว้น catch-all ไปยังระดับที่สูงขึ้น
  • พิจารณาAOPสำหรับวิธีแก้ปัญหาที่ซับซ้อนมากขึ้นเพื่อแก้ไขข้อกังวลข้ามเช่นการบันทึกและการจัดการข้อยกเว้น (แต่ซับซ้อนกว่าการมีเพียงสองบรรทัดในบล็อก catch ของคุณ;)

+1 สำหรับ "มากมายและไม่น่าสนใจ" AOP คืออะไร
Tulains Córdova

@ user61852 การเขียนโปรแกรมเชิงภาพ (ฉันเพิ่มลิงค์) คำถามนี้แสดงตัวอย่างหนึ่ง w / r / t AOP และการล็อกอินใน Java: stackoverflow.com/questions/15746676/logging-with-aop-in-spring
Dan1701

11

ดังนั้นในฐานะโปรแกรมเมอร์ฉันหลีกเลี่ยงการทำซ้ำ [... ]

มีอันตรายที่นี่เมื่อใดก็ตามที่แนวคิดของ"don't repeat yourself"ถูกนำไปเล็กน้อยอย่างจริงจังไปยังจุดที่มันกลายเป็นกลิ่น


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

1
นั่นเป็นการแทงที่ดีมากที่ DRY ฉันต้องยอมรับว่าฉันเป็น DRYholic ตอนนี้ฉันจะพิจารณาสองครั้งเมื่อฉันคิดว่าจะย้ายโค้ด 5 บรรทัดไปที่อื่นเพื่อประโยชน์ของ DRY
SZT

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

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

6

เพื่อ echo @ Dan1701

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

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

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

ดังนั้นในกรณีนี้ฉันคิดว่า "SRP" สำคัญกว่า "DRY"


1
[...]"SRP" trumps "DRY"- ฉันคิดว่าคำพูดนี้ค่อนข้างสมบูรณ์แบบทีเดียว

@Ike พูดว่า ... นี่คือเหตุผลที่ฉันกำลังมองหา
Bruno Brant

+1 สำหรับการชี้ให้เห็นว่าการเปลี่ยนบริบทของการบันทึกจะเข้าสู่ระบบราวกับว่าคลาสยกเว้นเป็นจุดเริ่มต้นหรือรายการบันทึกซึ่งไม่ใช่
Tulains Córdova

2

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

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

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


1

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

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

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

อย่างไรก็ตามเป็นวิธีการที่ดีในการใช้การยกเว้นเป็นค่าเฉลี่ยเพื่อทำเครื่องหมายชั้นแอปพลิเคชันที่มีข้อผิดพลาดปรากฏขึ้น

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

interface ICache<T, U>
{
    T GetValueByKey(U key); // may throw an CacheException
}

class FileCache<T, U> : ICache<T, U>
{
    T GetValueByKey(U key)
    {
        throw new CacheException("Could not retrieve object from FileCache::getvalueByKey. The File could not be opened. Key: " + key);
    }
}

class RedisCache<T, U> : ICache<T, U>
{
    T GetValueByKey(U key)
    {
        throw new CacheException("Could not retrieve object from RedisCache::getvalueByKey. Failed connecting to Redis server. Redis server timed out. Key: " + key);
    }
}

class CacheableInt
{
    ICache<int, int> cache;
    ILogger logger;

    public CacheableInt(ICache<int, int> cache, ILogger logger)
    {
        this.cache = cache;
        this.logger = logger;
    }

    public int GetNumber(int key) // may throw service exception
    {
        int result;

        try {
            result = this.cache.GetValueByKey(key);
        } catch (Exception e) {
            this.logger.Error(e);
            throw new ServiceException("CacheableInt::GetNumber failed, because the cache layer could not respond to request. Key: " + key);
        }

        return result;
    }
}

class CacheableIntService
{
    CacheableInt cacheableInt;
    ILogger logger;

    CacheableInt(CacheableInt cacheableInt, ILogger logger)
    {
        this.cacheableInt = cacheableInt;
        this.logger = logger;
    }

    int GetNumberAndReturnCode(int key)
    {
        int number;

        try {
            number = this.cacheableInt.GetNumber(key);
        } catch (Exception e) {
            this.logger.Error(e);
            return 500; // error code
        }

        return 200; // ok code
    }
}

สมมติว่ามีคนเรียกรหัสGetNumberAndReturnCodeและรับ500รหัสเพื่อส่งสัญญาณข้อผิดพลาด เขาจะเรียกฝ่ายสนับสนุนที่จะเปิดไฟล์บันทึกและดูนี้:

ERROR: 12:23:27 - Could not retrieve object from RedisCache::getvalueByKey. Failed connecting to Redis server. Redis server timed out. Key: 28
ERROR: 12:23:27 - CacheableInt::GetNumber failed, because the cache layer could not respond to request. Key: 28

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

บางทีผู้ใช้รายอื่นอาจเรียกวิธีเดียวกันนี้รับ500รหัสด้วยเช่นกัน แต่บันทึกจะแสดงดังต่อไปนี้:

INFO: 11:11:11- Could not retrieve object from RedisCache::getvalueByKey. Value does not exist for the key 28.
INFO: 11:11:11- CacheableInt::GetNumber failed, because the cache layer could not find any data for the key 28.

ในกรณีนี้ฝ่ายสนับสนุนสามารถตอบสนองต่อผู้ใช้ว่าการร้องขอนั้นไม่ถูกต้องเพราะเขากำลังขอค่าสำหรับ ID ที่ไม่มีอยู่จริง


สรุป

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


1

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

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


1

ฉันรู้ว่ามันเป็นเธรดเก่า แต่ฉันเพิ่งเจอปัญหาที่คล้ายกันและหาวิธีแก้ปัญหาที่คล้ายกันดังนั้นฉันจะเพิ่ม 2 เซ็นต์ของฉัน

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

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


0

นี่คือรูปแบบการต่อต้าน

ในความคิดของการโทรเข้าสู่ระบบสร้างของข้อยกเว้นจะเป็นตัวอย่างของการต่อไปนี้: ข้อบกพร่อง: สร้างไม่จริงการทำงาน

ฉันไม่เคยคาดหวังว่าจะเป็นผู้สร้าง (หรือปรารถนา) ผู้ให้บริการภายนอก นั่นเป็นผลข้างเคียงที่ไม่พึงประสงค์ซึ่งMiško Hevery ชี้ให้เห็นบังคับให้ subclasses และ mocks ทำการสืบทอดพฤติกรรมที่ไม่พึงประสงค์

เช่นนี้มันจะละเมิดหลักการของความประหลาดใจน้อยที่สุด

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

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