C ++ में एरर हैंडलिंग के लिए try-catch या ifs


30

गेम इंजन डिज़ाइन में व्यापक रूप से उपयोग किए जाने वाले अपवाद हैं या बयानों के अनुसार शुद्ध का उपयोग करना अधिक बेहतर है? अपवादों के साथ उदाहरण के लिए:

try {
    m_fpsTextId = m_statistics->createText( "FPS: 0", 16, 20, 20, 1.0f, 1.0f, 1.0f );
    m_cpuTextId = m_statistics->createText( "CPU: 0%", 16, 20, 40, 1.0f, 1.0f, 1.0f );
    m_frameTimeTextId = m_statistics->createText( "Frame time: 0", 20, 20, 60, 1.0f, 1.0f, 1.0f );
    m_mouseCoordTextId = m_statistics->createText( "Mouse: (0, 0)", 20, 20, 80, 1.0f, 1.0f, 1.0f );
    m_cameraPosTextId = m_statistics->createText( "Camera pos: (0, 0, 0)", 40, 20, 100, 1.0f, 1.0f, 1.0f );
    m_cameraRotTextId = m_statistics->createText( "Camera rot: (0, 0, 0)", 40, 20, 120, 1.0f, 1.0f, 1.0f );
} catch ... {
    // some info about error
}

और अगर साथ:

m_fpsTextId = m_statistics->createText( "FPS: 0", 16, 20, 20, 1.0f, 1.0f, 1.0f );
if( m_fpsTextId == -1 ) {
    // show error
}
m_cpuTextId = m_statistics->createText( "CPU: 0%", 16, 20, 40, 1.0f, 1.0f, 1.0f );
if( m_cpuTextId == -1 ) {
    // show error
}
m_frameTimeTextId = m_statistics->createText( "Frame time: 0", 20, 20, 60, 1.0f, 1.0f, 1.0f );
if( m_frameTimeTextId == -1 ) {
    // show error
}
m_mouseCoordTextId = m_statistics->createText( "Mouse: (0, 0)", 20, 20, 80, 1.0f, 1.0f, 1.0f );
if( m_mouseCoordTextId == -1 ) {
    // show error
}
m_cameraPosTextId = m_statistics->createText( "Camera pos: (0, 0, 0)", 40, 20, 100, 1.0f, 1.0f, 1.0f );
if( m_cameraPosTextId == -1 ) {
    // show error
}
m_cameraRotTextId = m_statistics->createText( "Camera rot: (0, 0, 0)", 40, 20, 120, 1.0f, 1.0f, 1.0f );
if( m_cameraRotTextId == -1 ) {
    // show error
}

मैंने सुना है कि अपवाद ifs की तुलना में थोड़ा धीमा हैं और मुझे if-checking विधि के साथ अपवादों को नहीं मिलाया जाना चाहिए। हालाँकि अपवादों के साथ मेरे पास हर इनिशियलाइज़ () पद्धति या कुछ समान के बाद के टन के साथ अधिक पठनीय कोड है, हालांकि वे कभी-कभी मेरी राय में सिर्फ एक विधि के लिए बहुत भारी होते हैं। क्या वे खेल के विकास के लिए ठीक हैं या साधारण इफ्स के साथ रहना बेहतर है?


बहुत से लोग दावा करते हैं कि बूस्ट खेल के विकास के लिए बुरा है, लेकिन मैं आपको ["Boost.optional"] ( boost.org/doc/libs/1_52_0/libs/optional/doc/html/index.html ) देखने का सुझाव दूंगा। आपके
ifs

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

जवाबों:


48

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

अपवादों का उपयोग न करें। कारणों की अधिकता है। मैं प्रमुख लोगों की सूची दूंगा।

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

अपवाद कोड को और अधिक नाजुक बनाते हैं । एक बदनाम ग्राफिक है (जिसे मैं दुखद अभी नहीं पा सकता हूं) जो मूल रूप से एक चार्ट पर केवल अपवाद-सुरक्षित कोड बनाम अपवाद-असुरक्षित कोड लिखने की कठिनाई को दर्शाता है, और पूर्व एक काफी बड़ा बार है। अपवादों के साथ बस बहुत सा गौचे हैं, और कोड लिखने के कई तरीके हैं जो अपवाद सुरक्षित दिखते हैं लेकिन वास्तव में ऐसा नहीं है। यहां तक ​​कि पूरी C ++ 11 समिति ने इस एक पर नासमझी की और std का उपयोग करने के लिए एक महत्वपूर्ण सहायक कार्य जोड़ना भूल गई :: unique_ptr सही ढंग से, और यहां तक ​​कि उन सहायक कार्यों के साथ, उन्हें उपयोग करने के लिए अधिक टाइपिंग नहीं की तुलना में और अधिकांश प्रोग्रामर जीते। टी भी एहसास है कि क्या गलत है अगर वे नहीं करते हैं।

विशेष रूप से खेल उद्योग के लिए, कुछ कंसोल के वेंडर द्वारा आपूर्ति किए गए संकलक / रनटाइम पूरी तरह से अपवाद का समर्थन नहीं करते हैं, या यहां तक ​​कि उन्हें बिल्कुल भी समर्थन नहीं करते हैं। यदि आपका कोड अपवादों का उपयोग करता है, तो क्या आप इस दिन भी रह सकते हैं और उम्र को नए प्लेटफार्मों पर पोर्ट करने के लिए आपके कोड के कुछ हिस्सों को फिर से लिखना होगा। (पता लगाएँ कि अगर यह 7 साल में बदल गया है क्योंकि कहा गया है कि कंसोल जारी किए गए हैं; हम सिर्फ अपवादों का उपयोग नहीं करते हैं, उन्हें कंपाइलर सेटिंग्स में भी अक्षम कर दिया है, इसलिए मुझे नहीं पता कि मैं जिस किसी से भी बात करता हूं, उसने हाल ही में जाँच की है।)

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

अपने उदाहरण की तरह कोड के बारे में, आपके पास समस्या को ठीक करने के कई अन्य तरीके हैं। अधिक मजबूत में से एक - हालांकि जरूरी नहीं कि आपके सरल उदाहरण में सबसे आदर्श है - मौद्रिक त्रुटि प्रकारों की ओर झुकाव है। यही है, createText () पूर्णांक के बजाय एक कस्टम हैंडल प्रकार वापस कर सकता है। इस हैंडल प्रकार में पाठ को अपडेट या नियंत्रित करने के लिए एक्सेसर्स हैं। यदि हैंडल को एक त्रुटि स्थिति में डाल दिया जाता है (क्योंकि createText () विफल) तो हैंडल पर आगे कॉल चुपचाप विफल हो जाती है। आप यह देखने के लिए कि यह त्रुटिपूर्ण है, और यदि ऐसा है, तो अंतिम त्रुटि क्या थी, यह देखने के लिए आप हैंडल को क्वेरी कर सकते हैं। इस दृष्टिकोण में अन्य विकल्पों की तुलना में अधिक ओवरहेड है, लेकिन यह काफी ठोस है। ऐसे मामलों में इसका उपयोग करें, जब आपको किसी संदर्भ में ऑपरेशन की एक लंबी स्ट्रिंग करने की आवश्यकता होती है, जहां कोई भी एकल ऑपरेशन उत्पादन में विफल हो सकता है, लेकिन जहां आप नहीं करते / जीत नहीं सकते हैं '

कस्टम त्रुटि ऑब्जेक्ट्स का उपयोग करने के बजाय, मौद्रिक त्रुटि हैंडलिंग को लागू करने का एक विकल्प है, संदर्भ ऑब्जेक्ट पर विधियों को अवैध रूप से हैंडल आईडी के साथ इनायत से व्यवहार करें। उदाहरण के लिए, अगर createText () रिटर्न -1 जब यह विफल हो जाता है, तो m_statistics के लिए कोई भी अन्य कॉल जो उन हैंडल्स में से एक को लेती है, को इनायत से बाहर निकल जाना चाहिए यदि -1 में पास किया गया हो।

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

कॉल के लिए सबसे अच्छा विकल्प (पहले से ही उपरोक्त लेखों की श्रृंखला में प्रस्तुत किया गया है) कि आप किसी भी समझदार वातावरण में विफल होने की उम्मीद नहीं करते हैं - जैसे कि एक सांख्यिकी प्रणाली के लिए पाठ बूँदें बनाने का सरल कार्य - बस फ़ंक्शन करना है वह विफल (आपके उदाहरण में createText) गर्भपात। आप उचित रूप से सुनिश्चित कर सकते हैं कि createText () उत्पादन में विफल नहीं होने वाला है जब तक कि कुछ पूरी तरह से जीता नहीं जाता है (उदाहरण के लिए, उपयोगकर्ता ने फ़ॉन्ट डेटा फ़ाइलों को हटा दिया है, या किसी कारण से केवल 256MB मेमोरी, आदि)। इनमें से कई मामलों में, विफलता होने पर भी कोई अच्छी बात नहीं है। स्मृति से बाहर? आप उपयोगकर्ता को OOM त्रुटि दिखाने के लिए एक अच्छा GUI पैनल बनाने के लिए आवश्यक आवंटन करने में भी सक्षम नहीं हो सकते हैं। फोंट गुम? उपयोगकर्ता के लिए त्रुटियों को प्रदर्शित करना कठिन बनाता है। जो भी गलत है,

जब तक आप (ए) लॉग फ़ाइल में त्रुटि लॉग करते हैं तब तक दुर्घटना पूरी तरह से ठीक है और (बी) केवल उन त्रुटियों पर करें जो नियमित उपयोगकर्ता क्रियाओं के कारण नहीं होती हैं।

मैं कई सर्वर ऐप्स के लिए एक ही बात नहीं कहूंगा, जहां उपलब्धता महत्वपूर्ण है और वॉचडॉग मॉनिटरिंग पर्याप्त नहीं है, लेकिन यह गेम क्लाइंट के विकास से काफी अलग है । मैं आपको C / C ++ का उपयोग करने के लिए भी दृढ़ता से स्पष्ट करूंगा क्योंकि अन्य भाषाओं की अपवाद हैंडलिंग सुविधाएं C ++ की तरह नहीं काटती हैं क्योंकि वे प्रबंधित वातावरण हैं और C ++ के पास सभी अपवाद-सुरक्षा समस्याएं नहीं हैं। किसी भी प्रदर्शन संबंधी चिंताओं को भी कम कर दिया जाता है क्योंकि सर्वर गेम क्लाइंट की तरह न्यूनतम विलंबता गारंटी की तुलना में समानता और थ्रूपुट पर अधिक ध्यान केंद्रित करते हैं। यहां तक ​​कि निशानेबाजों के लिए एक्शन गेम सर्वर और जैसे सी # में लिखे जाने पर काफी अच्छी तरह से कार्य कर सकते हैं, उदाहरण के लिए, क्योंकि वे हार्डवेयर को अपनी सीमा तक धकेल रहे हैं जैसे एफपीएस क्लाइंट करते हैं।


1
+1। इसके अलावा कुछ कंपाइलरों warn_unused_resultमें कार्य करने के लिए एट्रीब्यूट जोड़ने की संभावना है जो गैर-संभाला त्रुटि कोड को पकड़ने की अनुमति देता है।
मैकीज पीचोटका

शीर्षक में C ++ देखकर यहां आया, और पूरी तरह से यकीन है कि मौद्रिक त्रुटि से निपटने पहले से ही यहां नहीं होगा। अच्छी चीज़!
एडम

उन जगहों के बारे में जहां प्रदर्शन महत्वपूर्ण नहीं है और आप ज्यादातर तेजी से कार्यान्वयन के लिए लक्ष्य कर रहे हैं? उदाहरण के लिए जब आप खेल तर्क को लागू कर रहे हैं?
अली

यह भी कि केवल डिबगिंग उद्देश्य के लिए अपवाद हैंडलिंग का उपयोग करने के विचार के बारे में क्या? जब आप गेम को रिलीज़ करते हैं, तो आप समस्याओं के बिना आसानी से जाने की उम्मीद करते हैं। इसलिए आप विकास के दौरान बग को खोजने और ठीक करने के लिए अपवादों का उपयोग करते हैं और बाद में रिलीज़ मोड में उन सभी अपवादों को हटा देते हैं।
अली

1
@ गजु: खेल तर्क के लिए, अपवाद केवल तर्क का अनुसरण करने के लिए कठिन बनाते हैं (क्योंकि यह सभी कोड का पालन करना कठिन बनाता है)। यहां तक ​​कि पायथन और सी # अपवाद में, मेरे अनुभव में, खेल तर्क के लिए दुर्लभ हैं। डिबगिंग के लिए, कठिन मुखर आमतौर पर अधिक सुविधाजनक तरीका है। सटीक क्षण में ब्रेक और स्टॉप कुछ गलत हो जाता है, न कि स्टैक के बड़े हिस्से को खोल देने के बाद और सिमेंटिक फेंकने के अपवाद के कारण सभी प्रकार की प्रासंगिक जानकारी खोना। डिबगिंग लॉजिक के लिए, आप टूल और इंफॉर्मेशन / GUIs को एडिट करना चाहते हैं, इसलिए डिज़ाइनर निरीक्षण और ट्विक कर सकते हैं।
शॉन मिडिल्डिच

13

डोनाल्ड नथ का पुराना ज्ञान स्वयं है:

"हमें छोटी अक्षमताओं के बारे में भूलना चाहिए, समय के 97% के बारे में कहना चाहिए: समय से पहले अनुकूलन सभी बुराई की जड़ है।"

इसे एक बड़े पोस्टर के रूप में प्रिंट करें और इसे कहीं भी लटका दें आप गंभीर प्रोग्रामिंग करते हैं।

तो भी अगर कोशिश / पकड़ थोड़ी धीमी है तो मैं इसे डिफ़ॉल्ट रूप से उपयोग करूंगा:

  • कोड को यथासंभव पठनीय और समझने योग्य होना चाहिए। आप हो सकता है एक बार कोड लिखने, लेकिन आप होगा इस कोड डिबगिंग, समझने के लिए अन्य कोड डिबगिंग, सुधार के लिए, के लिए के लिए यह कई गुना अधिक पढ़ा, ...

  • प्रदर्शन पर सुधार। अगर / और सामान सही ढंग से करना तुच्छ नहीं है। आपके उदाहरण में यह गलत तरीके से किया गया है, क्योंकि एक त्रुटि को नहीं दिखाया जा सकता है, लेकिन कई। आपको कैस्केड का उपयोग करना है अगर / फिर / अन्यथा।

  • लगातार उपयोग करने में आसान: कोशिश / पकड़ एक ऐसी शैली को गले लगाती है जहां आपको हर पंक्ति में त्रुटियों की जांच करने की आवश्यकता नहीं होती है। दूसरी ओर: एक अगर / बाकी है और अपने कोड हो सकता है मलबे कहर याद आ रही। बेशक केवल अपरिवर्तनीय परिस्थितियों में।

तो अंतिम बिंदु:

मैंने सुना है कि अपवाद इफ़्स की तुलना में थोड़ा धीमा हैं

मैंने सुना है कि लगभग 15 साल पहले, हाल ही में किसी भी विश्वसनीय स्रोतों से नहीं सुना है। कंपाइलर में सुधार हो सकता है या जो भी हो।

मुख्य बिंदु है: समय से पहले अनुकूलन न करना। इसे केवल तभी करें जब आप बेंचमार्क द्वारा साबित कर सकते हैं कि हाथ में कोड कुछ भारी उपयोग किए गए कोड का तंग आंतरिक लूप है और यह स्विचिंग शैली में काफी सुधार करता है । यह 3% मामला है।


22
मैं अनंत के लिए यह -1 होगा। मैं इस नुथ बोली को डेवलपर दिमाग के लिए किए गए नुकसान की थाह नहीं लगा सकता। यह देखते हुए कि अपवादों का उपयोग करना समय से पहले अनुकूलन नहीं है, यह एक डिजाइन निर्णय है, एक महत्वपूर्ण डिजाइन निर्णय है जिसमें प्रदर्शन से परे बहुत अधिक बदलाव हैं।
sam hocevar

3
@SamHocevar: मुझे क्षमा करें, मुझे आपकी बात नहीं आती: अपवाद का उपयोग करते समय संभावित प्रदर्शन हिट के बारे में सवाल है । मेरी बात: इसके बारे में मत सोचो, हिट इतना बुरा नहीं है (यदि कोई हो) और अन्य सामान बहुत अधिक महत्वपूर्ण है। यहाँ आप मेरी सूची में "डिज़ाइन निर्णय" जोड़कर सहमत प्रतीत होते हैं। ठीक है। दूसरी ओर, आप कहते हैं कि नथ का उद्धरण बुरा है, जिसका अर्थ है कि समय से पहले अनुकूलन खराब नहीं है। लेकिन यह वास्तव में यहाँ क्या हुआ है आईएमओ: क्यू वास्तुकला, डिजाइन या विभिन्न एल्गोरिदम के बारे में नहीं सोचता है, केवल अपवादों और उनके प्रदर्शन प्रभाव के बारे में।
एएच

2
मैं C ++ में स्पष्टता से असहमत हूं। C ++ में अपवाद अनियंत्रित हैं जबकि कुछ संकलक जाँच किए गए रिटर्न मानों को लागू करते हैं। परिणामस्वरूप आपके पास ऐसा कोड है जो स्पष्ट दिखता है, लेकिन इसमें मेमोरी मेमोरी छिपी हो सकती है या यहां तक ​​कि अपरिभाषित अवस्था में भी कोड रखा जा सकता है। साथ ही तृतीय-पक्ष कोड अपवाद सुरक्षित नहीं हो सकता है। (जावा / सी # में स्थिति अलग है जिन्होंने अपवादों की जाँच की है, जीसी, ...)। डिजाइन निर्णय के अनुसार - अगर वे एपीआई को पार नहीं करते हैं तो प्रत्येक शैली के लिए / से रिफलैक्टिंग अर्ध-स्वचालित रूप से पर्ल वन-लाइनर्स के साथ की जा सकती है।
मैकीज पीचोटका

1
@SamHocevar: " सवाल इस बारे में है कि बेहतर क्या है, बेहतर नहीं है। " प्रश्न के अंतिम पैराग्राफ को फिर से पढ़ें। केवल कारण वह भी सोच रहा है के बारे में अपवाद का उपयोग नहीं क्योंकि वह सोचता है कि वे धीमी हो सकती है है। अब अगर आपको लगता है कि अन्य चिंताएँ हैं, तो ओपी ने ध्यान नहीं दिया, बेझिझक उन्हें पोस्ट करें या उन लोगों को उकसाएं जिन्होंने ऐसा किया था। लेकिन अपवादों के प्रदर्शन पर ओपी बहुत स्पष्ट रूप से केंद्रित है।
निकोल बोल्स

3
@ आह - यदि आप नुथ को उद्धृत करने जा रहे हैं, तो इसके बाकी हिस्सों (और IMO सबसे महत्वपूर्ण हिस्सा) को मत भूलना: " फिर भी हमें उस महत्वपूर्ण 3% में अपने अवसरों को पारित नहीं करना चाहिए। एक अच्छा प्रोग्रामर नहीं होगा। इस तरह के तर्क से शालीनता से लबरेज, वह महत्वपूर्ण कोड को ध्यान से देखने के लिए समझदार होगा; लेकिन उसके बाद ही उसकी पहचान की गई है । " सभी भी अक्सर इसे एक अनुकूलन के रूप में नहीं देखते हैं, या उन मामलों में धीमी गति से होने वाले औचित्य कोड की कोशिश करते हैं जहां प्रदर्शन एक अनुकूलन नहीं है, लेकिन वास्तव में यह एक मूलभूत आवश्यकता है।
मैक्सिमस मिनिमस

10

इस प्रश्न का पहले से ही बहुत बढ़िया उत्तर है, लेकिन मैं आपके विशेष मामले में कोड पठनीयता और त्रुटि सुधार के बारे में कुछ विचार जोड़ना चाहूंगा।

मेरा मानना ​​है कि आपका कोड वास्तव में इस तरह दिख सकता है:

m_fpsTextId = m_statistics->createText( "FPS: 0", 16, 20, 20, 1.0f, 1.0f, 1.0f );
m_cpuTextId = m_statistics->createText( "CPU: 0%", 16, 20, 40, 1.0f, 1.0f, 1.0f );
m_frameTimeTextId = m_statistics->createText( "Frame time: 0", 20, 20, 60, 1.0f, 1.0f, 1.0f );
m_mouseCoordTextId = m_statistics->createText( "Mouse: (0, 0)", 20, 20, 80, 1.0f, 1.0f, 1.0f );
m_cameraPosTextId = m_statistics->createText( "Camera pos: (0, 0, 0)", 40, 20, 100, 1.0f, 1.0f, 1.0f );
m_cameraRotTextId = m_statistics->createText( "Camera rot: (0, 0, 0)", 40, 20, 120, 1.0f, 1.0f, 1.0f );

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

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