
প্রায় ৫০ বছর ধরে, switch statement (case statement নামেও পরিচিত) প্রোগ্রামিং-এর একটি অবিচ্ছেদ্য অংশ হয়ে আছে। তবে সাম্প্রতিক বছরগুলিতে, কেউ কেউ দাবি করছেন যে switch statement তার উপযোগিতা হারিয়েছে। অন্যরা আরও এগিয়ে গিয়ে switch statement-কে code-smell হিসেবে চিহ্নিত করছেন।
১৯৫২ সালে, Stephen Kleene তার গবেষণাপত্র Introduction to Metamathematics-এ switch statement-এর ধারণা দেন। প্রথম উল্লেখযোগ্য বাস্তবায়ন ছিল ১৯৫৮ সালে 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 ব্যবহার করা এবং object-গুলিকে নিজেদের সঠিক কোডের অংশে dispatch করতে দেওয়া। তখন আমি বুঝলাম যে এটি আসলে কোনো “ঘাটতি” নয়, বরং Smalltalk আমাকে C++-এ যা অভ্যস্ত হয়েছিলাম তার চেয়ে অনেক সূক্ষ্ম OOP ডিজাইনে বাধ্য করছিল। যদি switch statement উপলব্ধ থাকত তাহলে এটি শিখতে আমার অনেক বেশি সময় লাগত বা, আরও খারাপ, আমি এখনও Smalltalk-এ C++/Java pseudo-object স্টাইলে প্রোগ্রামিং করতাম।
আমি যুক্তি দেব যে সাধারণ OOP-তে switch statement-এর প্রকৃত প্রয়োজন নেই। কখনও কখনও, যখন non-OOP জগতের সাথে ইন্টারফেস করা হয় (যেমন WM_XXXX Windows messages গ্রহণ এবং dispatch করা যা object নয় বরং শুধু integer), তখন switch statement উপযোগী হবে। এই পরিস্থিতিতে, বিকল্প রয়েছে (যেমন Dictionary থেকে dispatching) এবং এগুলি যতবার আসে তা অতিরিক্ত syntax অন্তর্ভুক্ত করার যোগ্য নয়।
Andy কি ঠিক ছিলেন? switch statement ছাড়া আমরা কি ভাল আছি? অন্যান্য ভাষাগুলিও কি switch statement বাদ দিয়ে উপকৃত হবে?
এই প্রশ্নের উপর কিছু আলোকপাত করতে, আমি switch statement, dictionary, এবং polymorphism-এর মধ্যে একটি তুলনা তৈরি করেছি। আসুন এটিকে একটি smackdown বলি। সেরা বাস্তবায়ন জিতুক!
প্রতিটি বাস্তবায়নে একটি method রয়েছে যা একটি parameter নেয়, একটি integer, এবং একটি string return করে। আমরা প্রতিটি বাস্তবায়ন পরীক্ষা করতে cyclomatic complexity এবং maintainability index ব্যবহার করব। তারপর আমরা তিনটি বাস্তবায়নের একটি সামগ্রিক দৃষ্টিভঙ্গি নেব।
কোডটি।
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 পরিমাপ করে। এটি ০ থেকে ১০০-এর স্কেলে। সংখ্যা যত বেশি, তত ভাল।
Cyclomatic Complexity | Maintainability Index | |
---|---|---|
Switch Statement | 6 | 72 |
Dictionary | 3 | 73 |
Polymorphism | 15 | 94 |
আমরা প্রথমে cyclomatic complexity পরীক্ষা করব।
Cyclomatic complexity-এর ফলাফল সরল। Dictionary বাস্তবায়ন সবচেয়ে সহজ। এর মানে কি এটি সেরা সমাধান? না, যেমনটি আমরা maintainability index মূল্যায়ন করার সময় দেখব।
বেশিরভাগ মানুষ আমার মতোই ভাবতেন, সবচেয়ে কম cyclomatic complexity সহ বাস্তবায়নটি সবচেয়ে maintainable — এটি আর কীভাবে হতে পারে?
আমাদের পরিস্থিতিতে, সবচেয়ে কম cyclomatic complexity সহ বাস্তবায়নটি সবচেয়ে maintainable নয়। আসলে আমাদের পরিস্থিতিতে, এটি উল্টো। সবচেয়ে জটিল বাস্তবায়নটি সবচেয়ে maintainable! মন উড়ে গেল!
আপনি যদি মনে করেন, maintainability index score যত বেশি, তত ভাল। মূল কথায় আসলে, polymorphism-এর সেরা maintainability index score রয়েছে — কিন্তু এটির সর্বোচ্চ cyclomatic complexity-ও রয়েছে। কী ব্যাপার? এটি ঠিক মনে হচ্ছে না।
কেন সবচেয়ে জটিল বাস্তবায়নটি সবচেয়ে maintainable? এর উত্তর দিতে, আমাদের maintainability index বুঝতে হবে।
Maintainability index ৪টি metric নিয়ে গঠিত: cyclomatic complexity, lines of code, comments-এর সংখ্যা এবং Halstead volume। প্রথম তিনটি metric তুলনামূলকভাবে সুপরিচিত, কিন্তু শেষটি, Halstead Volume, তুলনামূলকভাবে অজানা। Cyclomatic complexity-এর মতো, Halstead Volume কোডের জটিলতা objectively পরিমাপ করার চেষ্টা করে।
সহজ কথায়, Halstead Volume কোডে moving parts-এর সংখ্যা (variables, system calls, arithmetic, coding constructs, ইত্যাদি) পরিমাপ করে। Moving parts যত বেশি, জটিলতা তত বেশি। Moving parts যত কম, জটিলতা তত কম। এটি ব্যাখ্যা করে কেন polymorphic বাস্তবায়ন maintainability index-এ উচ্চ স্কোর করে; class-গুলিতে খুব কম বা কোনো moving parts নেই। Halstead Volume দেখার আরেকটি উপায় হল এটি “moving parts” density পরিমাপ করে।
Software কী, যদি এটি পরিবর্তিত না হয়? বাস্তব জগতকে প্রতিফলিত করতে, আমরা পরিবর্তন আনছি। আমি প্রতিটি বাস্তবায়নে একটি নতুন রঙ যোগ করেছি।
নিচে সংশোধিত ফলাফল রয়েছে।
Cycolmatic Complexity | Maintainability Index | |
---|---|---|
Switch Statement | 7 | 70 |
Dictionary | 3 | 73 |
Polymorphism | 17 | 95 |
Switch statement এবং polymorphic approaches উভয়ই cyclomatic complexity-তে এক একক বৃদ্ধি পেয়েছে, কিন্তু আকর্ষণীয়ভাবে, dictionary বৃদ্ধি পায়নি। প্রথমে আমি এতে বিভ্রান্ত হয়েছিলাম, কিন্তু তারপর আমি বুঝলাম dictionary রঙগুলিকে data হিসেবে বিবেচনা করে এবং অন্য দুটি বাস্তবায়ন রঙগুলিকে 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 statement-এর ত্রুটিগুলি সম্পর্কে তার মতামত শেয়ার করেছেন:
১. Polymorphic method implementations একে অপরের থেকে lexically isolated। Variables যোগ, সরানো, পরিবর্তন করা যেতে পারে switch statement-এর অন্য branch-এ অসম্পর্কিত কোডে প্রভাব ফেলার কোনো ঝুঁকি ছাড়াই।
২. Polymorphic method implementations সঠিক জায়গায় ফিরে যাওয়ার গ্যারান্টি দেয়, ধরে নিয়ে যে তারা terminate হয়। C/C++/Java-এর মতো fall through ভাষায় switch statements-এর জন্য একটি error-prone “break” statement প্রয়োজন যাতে তারা পরবর্তী case block-এর পরিবর্তে switch-এর পরের statement-এ ফিরে যায়।
৩. Polymorphic method implementation-এর অস্তিত্ব compiler দ্বারা enforce করা যেতে পারে, যা polymorphic method implementation অনুপস্থিত থাকলে program compile করতে অস্বীকার করবে। Switch statements এমন কোনো exhaustiveness checking প্রদান করে না।
৪. Polymorphic method dispatching অন্যান্য source code-এর access (বা recompiling) ছাড়াই extensible। Switch statement-এ আরেকটি case যোগ করতে মূল dispatching code-এর access প্রয়োজন, শুধুমাত্র এক জায়গায় নয় বরং প্রতিটি জায়গায় যেখানে সংশ্লিষ্ট enum switch করা হচ্ছে।
৫. … আপনি switching apparatus থেকে স্বাধীনভাবে polymorphic methods পরীক্ষা করতে পারেন। লেখকের দেওয়া উদাহরণের মতো বেশিরভাগ functions যা switch করে তাতে অন্যান্য কোড থাকবে যা তখন আলাদাভাবে পরীক্ষা করা যায় না; অন্যদিকে, virtual method calls আলাদাভাবে পরীক্ষা করা যেতে পারে।
৬. Polymorphic method calls constant time dispatch-এর গ্যারান্টি দেয়। যা প্রাকৃতিকভাবে linear time construct (fall through সহ switch statement) কে constant time construct-এ রূপান্তর করার জন্য কোনো sufficiently smart compiler প্রয়োজন নেই।
দুর্ভাগ্যবশত, বা সৌভাগ্যবশত, আপনার camp-এর উপর নির্ভর করে, বেশিরভাগ ভাষায় switch statement রয়েছে, এবং তারা শীঘ্রই কোথাও যাচ্ছে না। এটি মাথায় রেখে, switch statements compile করার সময় hood-এর নিচে কী ঘটছে তা জানা ভাল।
তিনটি switch statement optimization ঘটতে পারে:
১. If-elseif statements – যখন switch statement-এ অল্প সংখ্যক cases বা sparse cases (non-incremental values, যেমন ১০, ২৫০, ১০০০) থাকে তখন এটি if-elseif statement-এ রূপান্তরিত হয়। ২. Jump Table – adjacent cases-এর বড় সেটে (১, ২, ৩, ৪, ৫) compiler switch statement-কে jump table-এ রূপান্তরিত করে। Jump Table মূলত memory-তে function-এর pointer (goto statement-এর মতো ভাবুন) সহ একটি Hashtable। ৩. Binary Search – sparse cases-এর বড় সেটের জন্য compiler case দ্রুত চিহ্নিত করতে binary search implement করতে পারে, database-এ index কীভাবে কাজ করে তার মতো। অসাধারণ ক্ষেত্রে যেখানে cases বিপুল সংখ্যক sparse এবং adjacent cases, compiler তিনটি optimization-এর সমন্বয় ব্যবহার করবে।
সারসংক্ষেপ
Object oriented জগতে ১৯৫২ সালে conceived switch statement software engineer-এর একটি mainstay। একটি উল্লেখযোগ্য ব্যতিক্রম হল Smalltalk যেখানে designers switch statement বাদ দেওয়ার সিদ্ধান্ত নিয়েছিলেন।
যখন বিকল্প সমতুল্য বাস্তবায়ন, dictionary, এবং polymorphism-এর সাথে তুলনা করা হয়, switch statement তেমন ভাল ফলাফল করেনি।
Switch statement এখানেই থাকবে, কিন্তু আমাদের তুলনা যেমন দেখিয়েছে switch statement-এর চেয়ে ভাল বিকল্প রয়েছে।
বাস্তবায়নগুলি Github-এ উপলব্ধ।
লেখক: চাক কনওয়ে সফটওয়্যার ইঞ্জিনিয়ারিং এবং জেনারেটিভ এআই-তে বিশেষজ্ঞ। তার সাথে সোশ্যাল মিডিয়ায় যোগাযোগ করুন: X (@chuckconway) অথবা তাকে YouTube-এ দেখুন।