क्या मुझे अपवाद फेंकने वाले निर्माणकर्ताओं पर त्रुटियों को लॉग करना चाहिए?


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);
}

इसलिए, जब भी मैं किसी अपवाद को फेंक रहा था या पकड़ रहा था, मैं उसे लॉग करूंगा। वास्तव में, यह लगभग सभी लॉगिंग था जो मैंने आवेदन पर किया था (आवेदन आरंभीकरण के लिए कुछ के अलावा)।

इसलिए, एक प्रोग्रामर के रूप में, मैं दोहराव को दूर करता हूं; इसलिए मैंने लकड़हारा कॉल को अपवाद निर्माण में स्थानांतरित करने का फैसला किया, इसलिए, जब भी मैं एक अपवाद का निर्माण कर रहा था, चीजें लॉग इन होंगी। मैं भी, निश्चित रूप से, एक अपवाद अपवाद बना सकता हूं जिसने मेरे लिए अपवाद को फेंक दिया, लेकिन इससे मेरे कोड की व्याख्या करना कठिन हो जाएगा और इससे भी बदतर, संकलक इसके साथ अच्छी तरह से निपटेंगे, यह महसूस करने में विफल होगा कि उस सदस्य को कॉल नहीं है। तुरंत फेंक दो।

तो, क्या यह एक विरोधी पैटर्न है? यदि हां, तो क्यों?


क्या होगा यदि आप अपवाद को क्रमबद्ध और निरूपित करते हैं? यह त्रुटि लॉग करेगा?
सर्वनाश

जवाबों:


18

यकीन नहीं है कि यह एक विरोधी पैटर्न के रूप में योग्य है, लेकिन IMO यह एक बुरा विचार है: यह लॉगिंग के अलावा एक अपवाद को जोड़ने के लिए अनावश्यक युग्मन है।

आप हमेशा दिए गए अपवाद के सभी उदाहरणों को लॉग इन नहीं करना चाहते हैं (हो सकता है कि यह इनपुट सत्यापन के दौरान होता है, जिसमें से लॉगिंग दोनों स्वैच्छिक और निर्बाध हो सकती है)।

दूसरे, आप विभिन्न लॉगिंग स्तरों के साथ एक त्रुटि की विभिन्न घटनाओं को लॉग करने का फैसला कर सकते हैं, जिस बिंदु पर आपको यह निर्दिष्ट करना होगा कि अपवाद का निर्माण करते समय, जो लॉगिंग व्यवहार के साथ अपवाद निर्माण को फिर से मैला करने का प्रतिनिधित्व करता है।

अंत में, यदि अपवाद किसी अन्य अपवाद के लॉगिंग के दौरान हुआ तो क्या होगा? क्या आप लॉग इन करेंगे? यह गड़बड़ हो जाता है ...

आपकी पसंद मूल रूप से हैं:

  • पकड़, लॉग और (पुनः) फेंक, जैसा कि आपने अपने उदाहरण में दिया था
  • आप दोनों के लिए करने के लिए एक ExceptionHelper क्लास बनाएं, लेकिन हेल्पर्स के पास एक कोड गंध है, और मैं इसकी सिफारिश नहीं करूंगा।
  • कैच-ऑल अपवाद हैंडलिंग को उच्च स्तर पर ले जाएं
  • क्रॉस-कटिंग चिंताओं जैसे कि लॉगिंग और अपवाद से निपटने के लिए एक अधिक परिष्कृत समाधान के लिए एओपी पर विचार करें (लेकिन आपके कैच ब्लॉक में उन दो लाइनों की तुलना में कहीं अधिक जटिल;)

"स्वैच्छिक और निर्बाध" के लिए +1। AOP क्या है?
ट्यूलेंस कोर्डोवा

@ user61852 पहलू-उन्मुख प्रोग्रामिंग (मैंने एक लिंक जोड़ा)। यह प्रश्न एक उदाहरण w / r / t AOP और जावा में लॉगिंग
दिखाता है

11

इसलिए, एक प्रोग्रामर के रूप में, मैं दोहराव से दूर [...]

जब भी इस अवधारणा को "don't repeat yourself"थोड़ा गंभीरता से लिया जाता है तो यह एक खतरा है जहां यह गंध बन जाता है।


2
अब मैं कैसे सही उत्तर चुनूं जब सभी अच्छे हों और एक-दूसरे पर निर्माण करें? ' अगर मैं इसके लिए एक कट्टरपंथी दृष्टिकोण लेता हूं तो DRY कैसे एक समस्या बन सकता है, इस पर उत्कृष्ट व्याख्या।
ब्रूनो ब्रांट

1
कि DRY में वास्तव में एक अच्छा छुरा है, मुझे स्वीकार करना चाहिए कि मैं एक DRYholic हूं। अब मैं दो बार विचार करूंगा जब मैं कोड की उन 5 पंक्तियों को डीआरवाई की खातिर कहीं और स्थानांतरित करने के बारे में सोचूंगा।
SZT

@ स्वमान I मूल रूप से बहुत समान था। उज्ज्वल पक्ष पर मुझे लगता है कि हम में से उन लोगों के लिए कहीं अधिक आशा है जो अतिरेक को बाहर निकालने की ओर बहुत दूर झुक जाते हैं, जो कहते हैं, कॉपी और पेस्ट का उपयोग करते हुए 500 लाइन फ़ंक्शन लिखें और इसे फिर से भरने के बारे में भी न सोचें। IMO को ध्यान में रखने वाली मुख्य बात यह है कि हर बार जब आप कुछ छोटे दोहराव पर मुहर लगाते हैं, तो आप कोड को विकेंद्रीकृत कर रहे हैं और एक निर्भरता को कहीं और पुनर्निर्देशित कर रहे हैं। यह या तो अच्छी बात हो सकती है या बुरी चीज। यह आपको व्यवहार को बदलने के लिए केंद्रीय नियंत्रण प्रदान करता है, लेकिन उस व्यवहार को साझा करना आपको काटने के लिए भी शुरू कर सकता है ...

@SZaman यदि आप एक बदलाव करना चाहते हैं, जैसे "केवल इस फ़ंक्शन को इसकी आवश्यकता है, तो अन्य जो इस केंद्रीय फ़ंक्शन का उपयोग करते हैं।" वैसे भी, यह एक संतुलन कार्य है जैसा कि मैं इसे देखता हूं - कुछ ऐसा जो पूरी तरह से करना मुश्किल है! लेकिन कभी-कभी थोड़ा सा दोहराव आपके कोड को अधिक स्वतंत्र और विघटित करने में मदद कर सकता है। और यदि आप एक कोड कोड का परीक्षण करते हैं और यह वास्तव में अच्छी तरह से काम करता है, भले ही यह कुछ बुनियादी तर्क को यहां और वहां दोहरा रहा हो, इसके कुछ कारण कभी भी बदल सकते हैं। इस बीच कुछ ऐसा है जो पूरी तरह से बाहरी चीजों पर निर्भर करता है और बहुत से बाहरी कारणों को बदलना पड़ता है।

6

इको @ Dan1701 को इको करने के लिए

यह चिंताओं को अलग करने के बारे में है - लॉगिंग को अपवाद में स्थानांतरित करना अपवाद और लॉगिंग के बीच तंग युग्मन बनाता है और इसका मतलब यह भी है कि आपने लॉगिंग के अपवाद के लिए एक अतिरिक्त जिम्मेदारी जोड़ दी है जो बदले में अपवाद वर्ग के लिए निर्भरता पैदा कर सकता है कि यह जरूरत नहीं है

रखरखाव के दृष्टिकोण से आप (मैं) यह तर्क देंगे कि आप इस तथ्य को छिपा रहे हैं कि अनुरक्षक से अपवाद को लॉग किया जा रहा है (कम से कम उदाहरण की तरह कुछ के संदर्भ में), आप संदर्भ भी बदल रहे हैं ( अपवाद के निर्माता के लिए अपवाद संचालक का स्थान) जो शायद है से नहीं है कि तुम क्या चाहते थे।

अंत में आप एक धारणा बना रहे हैं कि आप हमेशा एक अपवाद को लॉग इन करना चाहते हैं, ठीक उसी अंदाज में, जिस बिंदु पर उसका निर्माण / उत्थान हुआ है - जो शायद नहीं है। जब तक आपके पास लॉगिंग और नॉन-लॉगिंग अपवाद नहीं होंगे जो कि बहुत जल्दी से बहुत अप्रिय हो जाएंगे।

तो इस मामले में मुझे लगता है कि "एसआरपी" "डीआरवाई" ट्रम्प करता है।


1
[...]"SRP" trumps "DRY"- मुझे लगता है कि यह बोली बहुत अच्छी तरह से इसे पूरा करती है।

जैसा कि @ इके ने कहा ... यह उस तरह का औचित्य है जिसकी मुझे तलाश थी।
ब्रूनो ब्रांट

+1 यह इंगित करने के लिए कि लॉगिंग का संदर्भ बदलना लॉग करेगा जैसे कि अपवाद वर्ग मूल या लॉग प्रविष्टि है, जो नहीं है।
ट्यूलेंस कोर्डोवा

2

आपकी त्रुटि एक अपवाद है जहाँ आप इसे संभाल नहीं सकते हैं, और इस प्रकार यह नहीं जान सकते कि लॉगिंग इसे ठीक से संभालने का कोई हिस्सा है या नहीं।
ऐसे बहुत कम मामले हैं जहाँ आप त्रुटि का हिस्सा संभाल सकते हैं या उसमें शामिल होना चाहिए, जिसमें लॉगिंग शामिल हो सकती है, लेकिन फिर भी कॉल करने वाले को त्रुटि का संकेत देना चाहिए । यदि आप निम्नतम स्तर की रीडिंग करते हैं, तो उदाहरण बिना पढ़े-लिखे त्रुटियां हैं और पसंद करते हैं, लेकिन वे आम तौर पर सामान्य रूप से होते हैं कि कॉलर को संचारित जानकारी सुरक्षा और प्रयोज्य के लिए गंभीर रूप से फ़िल्टर की जाती है।

केवल एक चीज जिसे आप अपने मामले में कर सकते हैं, और किसी कारण के लिए करना है, अपवाद को लागू करने का अनुवाद कर रहा है एक कॉलर की उम्मीद में फेंकता है, संदर्भ के लिए मूल का पीछा करते हुए, और कुछ भी अकेले छोड़ देता है।

इसे सम्‍मिलित करने के लिए, आपके कोड ने अपवाद को आंशिक रूप से संभालने के लिए सही और कर्तव्य पर संदेह किया, इस प्रकार एसआरपी का उल्लंघन किया।
DRY इसमें नहीं आता है।


1

केवल एक अपवाद को रोकना क्योंकि आपने कैच ब्लॉक का उपयोग करके इसे लॉग इन करने का निर्णय लिया है (जिसका अर्थ है कि अपवाद बिल्कुल नहीं बदला है) एक बुरा विचार है।

हमारे द्वारा अपवादों, अपवाद संदेशों और इसके हैंडलिंग का उपयोग करने का एक कारण यह है कि हम जानते हैं कि क्या गलत हुआ और चतुराई से लिखे गए अपवाद एक बड़े अंतर से बग ढूंढने में तेजी ला सकते हैं।

यह भी याद रखें, अपवादों को संभालने से अधिक संसाधनों की लागत होती है, जैसे कि हमारे पास ऐसा है 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

फिर डेवलपर को तुरंत पता चल जाता है कि सॉफ्टवेयर की किस परत के कारण प्रक्रिया निरस्त हो गई और इस मुद्दे की पहचान करने का एक आसान तरीका है। इस मामले में यह महत्वपूर्ण है, क्योंकि रेडिस टाइमिंग को कभी भी खुश नहीं होना चाहिए।

शायद एक अन्य उपयोगकर्ता उसी विधि को कॉल करेगा, 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.

जिस स्थिति में समर्थन केवल उपयोगकर्ता को जवाब दे सकता है कि अनुरोध अमान्य था क्योंकि वह एक गैर-मौजूद आईडी के लिए एक मूल्य का अनुरोध कर रहा है।


सारांश

यदि आप अपवादों को संभाल रहे हैं, तो उन्हें सही तरीके से संभालना सुनिश्चित करें। यह भी सुनिश्चित करें कि आपके अपवाद में आपकी वास्तुकला परतों का पालन करते हुए, पहले स्थान पर सही डेटा / संदेश शामिल हैं, इसलिए संदेश आपको एक समस्या की पहचान करने में मदद करेंगे जो हो सकती है।


1

मुझे लगता है कि समस्या अधिक बुनियादी स्तर पर है: आप त्रुटि को लॉग करते हैं और इसे उसी स्थान पर अपवाद के रूप में फेंक देते हैं। वह प्रतिमान है। इसका मतलब है कि एक ही त्रुटि को कई बार लॉग किया जाता है यदि वह पकड़ा जाता है, शायद दूसरे अपवाद में लिपटे और फिर से फेंक दिया जाए।

इसके बजाय मैं त्रुटियों को लॉग करने का सुझाव देता हूं जब अपवाद नहीं बनाया जाता है, लेकिन जब पकड़ा जाता है। (इसके लिए, निश्चित रूप से, आपको यह सुनिश्चित करना चाहिए कि यह हमेशा कहीं न कहीं पकड़ा जाता है।) जब कोई अपवाद पकड़ा जाता है, तो मैं केवल इसके स्टैकट्रेस को लॉग इन करूंगा, अगर यह फिर से फेंका नहीं जाता है या किसी अन्य अपवाद में कारण के रूप में लपेटा जाता है। स्टैक ट्रैक्ट्स के स्टैकट्रैक और संदेश वैसे भी स्टैक ट्रैस में लॉग इन होते हैं, "वैसे भी ..."। और पकड़ने वाला भी यह तय कर सकता है कि पहली विफलता पर त्रुटि को लॉग किए बिना पुनः प्रयास करें, या बस इसे चेतावनी के रूप में मानें, या जो भी हो।


1

मुझे पता है कि यह एक पुराना धागा है, लेकिन मैं बस एक ऐसी ही समस्या के बारे में जानता हूं और एक समान समाधान का पता लगा रहा हूं, इसलिए मैं अपने 2 सेंट जोड़ूंगा।

मैं SRP उल्लंघन तर्क नहीं खरीदता। वैसे भी पूरी तरह से नहीं। आइए 2 चीजों को मानें: 1. आप वास्तव में अपवादों को लॉग इन करना चाहते हैं क्योंकि वे होते हैं (ट्रेस स्तर पर प्रोग्राम प्रवाह को फिर से बनाने में सक्षम होने के लिए)। अपवादों को संभालने से इसका कोई लेना-देना नहीं है। 2. आप उसके लिए एओपी का उपयोग नहीं कर सकते हैं या नहीं - मैं मानता हूं कि जाने का सबसे अच्छा तरीका होगा लेकिन दुर्भाग्य से मैं एक ऐसी भाषा के साथ फंस गया हूं जो उस के लिए उपकरण प्रदान नहीं करता है।

जिस तरह से मैं इसे देखता हूं, आपको मूल रूप से बड़े पैमाने पर एसआरपी उल्लंघन की सजा सुनाई जाती है, क्योंकि कोई भी वर्ग जो अपवादों को फेंकना चाहता है, उसे लॉग के बारे में पता होना चाहिए। अपवाद वर्ग में प्रवेश करना वास्तव में SRP उल्लंघन को बहुत कम करता है क्योंकि अब केवल अपवाद इसका उल्लंघन करता है और कोड आधार में हर वर्ग का नहीं।


0

यह एक विरोधी पैटर्न है।

मेरी राय में, एक अपवाद के निर्माता में एक लॉगिंग कॉल करना निम्नलिखित का एक उदाहरण होगा: दोष: निर्माणकर्ता वास्तविक कार्य

मैं एक बाहरी सेवा कॉल करने के लिए एक निर्माता से कभी भी (या इच्छा) की उम्मीद नहीं करूंगा। यह एक बहुत ही अवांछनीय दुष्परिणाम है, जैसा कि मिचो हेवेरी बताते हैं, उपवर्गों और नकली व्यवहारों को अवांछित व्यवहार के लिए मजबूर करता है।

जैसे, यह कम से कम विस्मय के सिद्धांत का भी उल्लंघन करेगा ।

यदि आप दूसरों के साथ एक एप्लिकेशन विकसित कर रहे हैं, तो यह साइड-इफ़ेक्ट संभवतः उनके लिए स्पष्ट नहीं होगा। यहां तक ​​कि अगर आप अकेले काम कर रहे हैं, तो आप इसके बारे में भूल सकते हैं और सड़क पर खुद को आश्चर्यचकित कर सकते हैं।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.