
लगभग 50 वर्षों से, switch statement (जिसे case statement के रूप में भी जाना जाता है) प्रोग्रामिंग का एक अभिन्न अंग रहा है। हाल के वर्षों में, हालांकि, कुछ लोग दावा कर रहे हैं कि switch statement ने अपनी उपयोगिता खो दी है। अन्य लोग switch statement को code-smell के रूप में लेबल करके और भी आगे जाते हैं।
1952 में, Stephen Kleene ने अपने पेपर Introduction to Metamathematics में switch statement की अवधारणा प्रस्तुत की। पहला उल्लेखनीय implementation 1958 में ALGOL 58 में था। बाद में, switch statement को अमिट C programming language में शामिल किया गया, जिसने, जैसा कि हम जानते हैं, अधिकांश आधुनिक प्रोग्रामिंग भाषाओं को प्रभावित किया है।
वर्तमान समय में तेजी से आगे बढ़ते हुए, वस्तुतः हर भाषा में switch statement है। हालांकि, कुछ भाषाओं ने switch statement को छोड़ दिया है। सबसे उल्लेखनीय Smalltalk है।
इसने मेरी जिज्ञासा जगाई, Smalltalk से switch statement क्यों बाहर रखा गया था?
Andy Bower, Dolphin Smalltalk के पीछे के निर्माताओं/समर्थकों में से एक ने Smalltalk द्वारा switch statement को बाहर रखने के कारणों पर अपने विचार साझा किए:
जब मैं पहली बार C++ से Smalltalk आया, तो मैं समझ नहीं पा रहा था कि एक कथित रूप से पूर्ण विकसित भाषा switch/case construct का समर्थन कैसे नहीं करती। आखिरकार जब मैं पहली बार BASIC से “structured programming” में गया तो मुझे लगा कि switch sliced bread के बाद से सबसे अच्छी चीजों में से एक था। हालांकि, क्योंकि Smalltalk switch का समर्थन नहीं करता था, मुझे इस कमी को दूर करने के लिए तरीके खोजने और समझने पड़े। सही उत्तर, निश्चित रूप से, polymorphism का उपयोग करना है और objects को स्वयं को सही कोड के टुकड़े पर dispatch करना है। तब मुझे एहसास हुआ कि यह बिल्कुल भी “कमी” नहीं थी, बल्कि Smalltalk मुझे C++ में आदी हो गए की तुलना में बहुत बेहतर OOP design में धकेल रहा था। यदि switch statement उपलब्ध होता तो मुझे इसे सीखने में बहुत अधिक समय लगता या, बदतर, मैं अभी भी Smalltalk में C++/Java pseudo-object style में प्रोग्रामिंग कर रहा होता।
मैं तर्क दूंगा कि सामान्य OOP में switch statement की कोई वास्तविक आवश्यकता नहीं है। कभी-कभी, जब non-OOP दुनिया के साथ interface करते समय (जैसे WM_XXXX Windows messages प्राप्त करना और dispatch करना जो objects नहीं बल्कि केवल integers हैं), तो switch statement उपयोगी होगा। इन स्थितियों में, विकल्प हैं (जैसे Dictionary से dispatching) और जितनी बार ये सामने आते हैं, वे अतिरिक्त syntax के inclusion को उचित नहीं ठहराते।
क्या Andy सही था? क्या हम switch statement के बिना बेहतर हैं? क्या अन्य भाषाओं को भी switch statement को बाहर रखने से फायदा होगा?
इस प्रश्न पर कुछ प्रकाश डालने के लिए, मैंने switch statement, dictionary, और polymorphism के बीच एक तुलना तैयार की है। आइए इसे smackdown कहते हैं। सबसे अच्छा implementation जीते!
प्रत्येक implementation में एक parameter, एक integer लेने वाला method है, और एक string return करता है। हम प्रत्येक implementation की जांच के लिए cyclomatic complexity और maintainability index का उपयोग करेंगे। फिर हम तीनों implementations का समग्र दृष्टिकोण लेंगे।
कोड।
Switch Statement
Maintainability Index | 72 |
---|---|
Cyclomatic Complexity | 6 |
public class SwitchWithFourCases
{
public string SwitchStatment(int color)
{
var colorString = "Red";
switch (color)
{
case 1:
colorString = "Green";
break;
case 2:
colorString = "Blue";
break;
case 3:
colorString = "Violet";
break;
case 4:
colorString = "Orange";
break;
}
return colorString;
}
}
Dictionary
Maintainability Index | 73 |
---|---|
Cyclomatic Complexity | 3 |
public class DictionaryWithFourItems
{
public string Dictionary(int color)
{
var colorString = "Red";
var colors = new Dictionary<int, string> {{1, "Green"}, {2, "Blue"}, {3, "Violet"}, {4, "Orange"}};
var containsKey = colors.ContainsKey(color);
if (containsKey)
{
colorString = colors[color];
}
return colorString;
}
}
Polymorphism
Total Maintainability Index | 94 |
---|---|
Total Cyclomatic Complexity | 15 |
Interface
Maintainability Index | 100 |
---|---|
Cyclomatic Complexity | 1 |
public interface IColor
{
string ColorName { get; }
}
Factory
Maintainability Index | 76 |
---|---|
Cyclomatic Complexity | 4 |
public class ColorFactory
{
public string GetColor(int color)
{
IColor defaultColor = new RedColor();
var colors = GetColors();
var containsKey = colors.ContainsKey(color);
if (containsKey)
{
var c = colors[color];
return c.ColorName;
}
return defaultColor.ColorName;
}
private static IDictionary<int, IColor> GetColors()
{
return new Dictionary<int, IColor>
{
{1, new GreenColor()},
{2, new BlueColor()},
{3, new VioletColor()},
{4, new OrangeColor()},
{5, new MagentaColor()}
};
}
}
Implementation
Maintainability Index | 97 |
---|---|
Cyclomatic Complexity | 2 |
public class BlueColor : IColor
{
public string ColorName => "Blue";
}
public class RedColor : IColor
{
public string ColorName => "Red";
}
public class GreenColor : IColor
{
public string ColorName => "Green";
}
public class MagentaColor : IColor
{
public string ColorName => "Magenta";
}
public class VioletColor : IColor
{
public string ColorName => "Violet";
}
परिणाम
परिणामों में जाने से पहले, आइए Cyclomatic Complexity और Maintainability Index को परिभाषित करते हैं:
- Cyclomatic Complexity logic branching का माप है। संख्या जितनी कम हो, उतना बेहतर।
- Maintainability Index कोड की maintainability को मापता है। यह 0 और 100 के बीच के पैमाने पर है। संख्या जितनी अधिक हो, उतना बेहतर।
Cyclomatic Complexity | Maintainability Index | |
---|---|---|
Switch Statement | 6 | 72 |
Dictionary | 3 | 73 |
Polymorphism | 15 | 94 |
हम पहले cyclomatic complexity की जांच करेंगे।
Cyclomatic complexity के परिणाम सीधे हैं। Dictionary implementation सबसे सरल है। क्या इसका मतलब यह है कि यह सबसे अच्छा समाधान है? नहीं, जैसा कि हम maintainability index का मूल्यांकन करते समय देखेंगे।
अधिकांश लोग मेरी तरह सोचते होंगे, सबसे कम cyclomatic complexity वाला implementation सबसे अधिक maintainable है — यह कैसे कोई और तरीका हो सकता है?
हमारे scenario में, सबसे कम cyclomatic complexity वाला implementation सबसे अधिक maintainable नहीं है। वास्तव में हमारे scenario में, यह विपरीत है। सबसे जटिल implementation सबसे अधिक maintainable है! दिमाग उड़ गया!
यदि आप याद करें, maintainability index score जितना अधिक हो, उतना बेहतर। मुख्य बात पर आते हुए, polymorphism का सबसे अच्छा maintainability index score है — लेकिन इसमें सबसे अधिक cyclomatic complexity भी है। क्या बात है? यह सही नहीं लगता।
सबसे जटिल implementation सबसे अधिक maintainable क्यों है? इसका उत्तर देने के लिए, हमें maintainability index को समझना होगा।
Maintainability index में 4 metrics शामिल हैं: cyclomatic complexity, lines of code, comments की संख्या और Halstead volume। पहले तीन metrics अपेक्षाकृत प्रसिद्ध हैं, लेकिन अंतिम, Halstead Volume, अपेक्षाकृत अज्ञात है। Cyclomatic complexity की तरह, Halstead Volume code complexity को objectively measure करने का प्रयास करता है।
सरल शब्दों में, Halstead Volume कोड में moving parts (variables, system calls, arithmetic, coding constructs, आदि) की संख्या को मापता है। Moving parts की संख्या जितनी अधिक हो, उतनी अधिक complexity। Moving parts की संख्या जितनी कम हो, उतनी कम complexity। यह बताता है कि polymorphic implementation maintainability index पर उच्च स्कोर क्यों करता है; classes में बहुत कम या कोई moving parts नहीं हैं। Halstead Volume को देखने का एक और तरीका यह है कि यह “moving parts” density को मापता है।
Software क्या है, यदि यह बदलने के लिए नहीं है? वास्तविक दुनिया को reflect करने के लिए, हम change introduce कर रहे हैं। मैंने प्रत्येक implementation में एक नया रंग जोड़ा है।
नीचे संशोधित परिणाम हैं।
Cycolmatic Complexity | Maintainability Index | |
---|---|---|
Switch Statement | 7 | 70 |
Dictionary | 3 | 73 |
Polymorphism | 17 | 95 |
Switch statement और polymorphic approaches दोनों में cyclomatic complexity एक unit बढ़ गई, लेकिन दिलचस्प बात यह है कि dictionary में वृद्धि नहीं हुई। पहले मैं इससे puzzled था, लेकिन फिर मुझे एहसास हुआ कि dictionary रंगों को data के रूप में मानता है और अन्य दो implementations रंगों को code के रूप में treat करते हैं। मैं मुख्य बात पर आऊंगा।
Maintainability index पर ध्यान देते हुए, केवल एक, switch statement, की maintainability में कमी आई। Polymorphism का maintainability score बेहतर हुआ और फिर भी complexity भी बढ़ती है (हम इसे कम होना पसंद करेंगे)। जैसा कि मैंने ऊपर उल्लेख किया, यह counter-intuitive है।
हमारी तुलना दिखाती है कि dictionaries, complexity के दृष्टिकोण से, अनंत तक scale कर सकती हैं। Polymorphic approach अब तक सबसे अधिक maintainable है और जैसे-जैसे अधिक scenarios जोड़े जाते हैं, maintainability में वृद्धि होती दिखती है। Switch statement complexity में वृद्धि करता है और जब नया scenario जोड़ा गया तो maintainability में कमी आती है। नया scenario जोड़ने से पहले भी, इसमें सबसे खराब cyclomatic complexity और maintainability index measures थे।
Google के Jem Finch ने switch statements की कमियों पर अपने विचार साझा किए:
1. Polymorphic method implementations lexically एक दूसरे से अलग हैं। Variables को जोड़ा, हटाया, संशोधित किया जा सकता है, और इसी तरह switch statement की किसी अन्य branch में unrelated code को प्रभावित करने के किसी भी जोखिम के बिना।
2. Polymorphic method implementations सही जगह वापस जाने की गारंटी देते हैं, यह मानते हुए कि वे terminate होते हैं। C/C++/Java जैसी fall through भाषा में Switch statements को यह सुनिश्चित करने के लिए error-prone “break” statement की आवश्यकता होती है कि वे अगले case block के बजाय switch के बाद के statement पर वापस जाएं।
3. Polymorphic method implementation का अस्तित्व compiler द्वारा enforce किया जा सकता है, जो यदि polymorphic method implementation गुम है तो program को compile करने से इनकार कर देगा। Switch statements ऐसी कोई exhaustiveness checking प्रदान नहीं करते।
4. Polymorphic method dispatching अन्य source code तक पहुंच (या recompiling) के बिना extensible है। Switch statement में एक और case जोड़ने के लिए मूल dispatching code तक पहुंच की आवश्यकता होती है, न केवल एक जगह बल्कि हर जगह जहां relevant enum पर switch किया जा रहा है।
5. … आप switching apparatus से स्वतंत्र रूप से polymorphic methods का test कर सकते हैं। अधिकांश functions जो author द्वारा दिए गए उदाहरण की तरह switch करते हैं, उनमें अन्य code होगा जिसे अलग से test नहीं किया जा सकता; दूसरी ओर, virtual method calls को अलग से test किया जा सकता है।
6. Polymorphic method calls constant time dispatch की गारंटी देते हैं। जो स्वाभाविक रूप से linear time construct (fall through के साथ switch statement) है उसे constant time construct में convert करने के लिए कोई sufficiently smart compiler आवश्यक नहीं है।
दुर्भाग्य से, या सौभाग्य से, आपके camp के आधार पर, अधिकांश भाषाओं में switch statement है, और वे जल्द ही कहीं नहीं जा रहे। इसे ध्यान में रखते हुए, यह जानना अच्छा है कि switch statements को compile करते समय hood के नीचे क्या हो रहा है।
तीन switch statement optimizations हैं जो हो सकते हैं:
- If-elseif statements – जब switch statement में cases की संख्या कम हो या sparse cases हों (non-incremental values, जैसे 10, 250, 1000) तो इसे if-elseif statement में convert कर दिया जाता है।
- Jump Table – adjacent cases के बड़े sets में (1, 2, 3, 4, 5) compiler switch statement को jump table में convert कर देता है। Jump Table मूल रूप से memory में function के pointer (goto statement की तरह सोचें) के साथ एक Hashtable है।
- Binary Search – sparse cases के बड़े sets के लिए compiler case को जल्दी identify करने के लिए binary search implement कर सकता है, जैसे database में index काम करता है। असाधारण cases में जहां cases sparse और adjacent cases की बड़ी संख्या हैं, compiler तीनों optimizations के combination का उपयोग करेगा।
सारांश
Object oriented दुनिया में 1952 में conceived switch statement software engineer का मुख्य आधार है। एक उल्लेखनीय अपवाद Smalltalk है जहां designers ने switch statement को exclude करने का विकल्प चुना।
जब alternative equivalent implementations, dictionary, और polymorphism से तुलना की गई, तो switch statement का प्रदर्शन उतना अच्छा नहीं था।
Switch statement यहां रहने के लिए है, लेकिन जैसा कि हमारी तुलना ने दिखाया है, switch statement के बेहतर विकल्प हैं।
Implementations Github पर उपलब्ध हैं।
लेखक: चक कॉनवे सॉफ्टवेयर इंजीनियरिंग और जेनेरेटिव AI में विशेषज्ञता रखते हैं। उनसे सोशल मीडिया पर जुड़ें: X (@chuckconway) या उन्हें YouTube पर देखें।