প্রতিটা বড় প্রজেক্টের কোডেই কম বেশি বাগ থাকে তবে কিছু নিয়ম মেনে কোড লিখলে বাগ কমিয়ে আনা যায়। সফটওয়্যার ইঞ্জিনিয়ারিং করতে গিয়ে সেরকম কিছু নিয়ম শিখেছি যেটা কনটেস্ট করতে গিয়ে শেখা হয় নি। এর মধ্যে সবথেকে গুরুত্বপূর্ণ আমার মনে হয়েছে অপরিবর্তনীয় বা ইমিউটেবল ভ্যারিয়েবল/অবজেক্টের ধারনাটা। এই লেখাটা সফটওয়্যার ইঞ্জিনিয়ার এবং অ্যাডভান্সড স্টুডেন্টদের জন্য, তুমি যদি এখনো বড় কোনো প্রজেক্টের কাজ না করে থাকো তাহলে ধারণাগুলো হয়তো ঠিকমতো বুঝতে পারবে না। আমি জাভা ব্যবহার করে কিছু উদাহরণ দিবো তবে ধারণাগুলো যেকোনো প্রোগ্রামিং ল্যাংগুয়েজের জন্য সত্য।
কোডে বাগ থাকার অন্যতম কারণ হলো ভ্যারিয়েবলগুলোর মান ক্রমাগত পরিবর্তন হতে থাকে। একটা ভ্যারিয়েবলের মান যদি ১০ নম্বর লাইনে একরকম হয়, ২০ নম্বর লাইনে আরেকরকম হয় তাহলে সেটা ডিবাগ করা বেশ কঠিন হয়ে যায়। সেটা থেকে বাচার উপায় হলো ইমিউটেবল (Immutable) ভ্যারিয়েবল। ইমিউটেবল ভ্যারিয়েবল হলো এমন একটা ভ্যারিয়েবল যার মান পরিবর্তন বা মিউটেট (mutate) করা যায় না, অর্থাৎ ভ্যারিয়েবলটি একটি constant। ঠিক সেরকম ইমিউটেবল অবজেক্টেরও কোনো পরিবর্তন করা যায়। সি++ এ আমরা const কিওয়ার্ড ব্যবহার করে ইমিউটেবল ভ্যারিয়েবল তৈরি করি, জাভাতে ব্যবহার করি final কিওয়ার্ড।
এখন তুমি বলতে পারো ভ্যারিয়েবলের মান পরিবর্তন না করে কোনো কাজ আগাবো কিভাবে? এটার উত্তর হলো ‘ট্রান্সফরমেশন‘। আমাদের যখনই ভ্যারিয়েবলের মান আপডেট করার দরকার হবে তখন আমরা নতুন একটা ভ্যারিয়েবল ব্যবহার করবো। খুবই সিম্পল একটা উদাহরণ দেখি:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
Employee processBonus(String userId) { Employee employee = database.get(userId); long salary = e.salary; //part A - 10 more lines salary += salary + performanceBonus; //part B - 10 more lines salary += salary + eidBonus; //part C - 10 more lines employee.setSalary(salary); //part D - 5 more lines return employee; } |
এই কোডে একজন এমপ্লোয়ির বোনাস ক্যালকুলেশন করা হচ্ছে। এই কোডে সমস্যা কি? আপাত দৃষ্টিতে কোনো সমস্যা নেই কিন্তু কেও যদি part C অংশটা ডিবাগ করার চেষ্টা করে তাকে কিন্তু খেয়াল রাখতে হবে উপরে কিভাবে salary এর মান পরিবর্তন হচ্ছে। যদি কোডের লজিক জটিল হয় তাহলে ডিবাগ করাটা বেশ কঠিন হয়ে যেতে পারে।
গুড প্র্যাকটিস হলো salary কে ফাইনাল ডিক্লেয়ার করা এবং যখন মান পরিবর্তন হচ্ছে তখন আরেকটা ভ্যারিয়েবলে অ্যাসাইন করে দেয়া যেটা হতে পারে এরকম:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
Employee processBonus(final String userId) { Employee employee = database.get(userId); final long salary = e.salary; //part A - 10 more lines final long salaryWithPerformanceBonus = salary + performanceBonus; //part B - 10 more lines final long finalSalaryWithAllBonus = salaryWithPerformanceBonus + eidBonus; //part C - 10 more lines employee.setSalary(finalSalaryWithAllBonus); //part D - 5 more lines return employee; } |
এবার এই কোডে এমপ্লোয়ির বেতনের ট্র্যাক রাখা অনেকটাই সহজ হয়ে যাবে ডেভেলপারের জন্য। salary ভ্যারিয়েবলটার মানের কোনোই পরিবর্তন হচ্ছে না, যখন কোনো আপডেট হচ্ছে তখন আরেকটা ভ্যারিয়েবল ব্যবহার করছি।
একই সাথে লক্ষ্য করো আমি ফাংশনের প্যারামিটার userId কেও ফাইনাল বানিয়ে দিয়েছি যাতে আমি নিশ্চিত থাকে আমি প্যারিমিটারের মান পরিবর্তন করে দিচ্ছি না।
এখনো এই কোডে সমস্যা আছে, সমস্যা হলো এই লাইনটায়:
1 |
employee.setSalary(finalSalaryWithAllBonus) |
এখানে আমরা employee অবজেক্টটার ভিতরের ভ্যারিয়েবলের মান পরিবর্তন করে দিচ্ছি যেটা কোডের রিডেবিলিটি আবারো কমিয়ে দিবে। আমরা চাইলে অবজেক্টটা এভাবে ডিক্লেয়ার করতে পারতাম:
1 |
final Employee employee = database.get(userId) |
এতে করে employee ভ্যারিয়েবলে নতুন কোনো অবজেক্ট অ্যাসাইন করা যাবে না। কিন্তু এখনো ক্লাসের ভ্যারিয়েবলগুলোর মান এখনো আপডেট করা যাবে।
এটার সমাধান করতে আমরা Employee ক্লাসটার দিকে তাকাই। সাধারণত জাভাতে আমরা এভাবে ক্লাস ডিক্লেয়ার করি:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
public class Employee { private String name; private Long salary; public Employee(String name, Long salary) { this.name = name; this.salary = salary; } public void setName(String name) { this.name = name; } public void setSalary(Long salary) { this.salary = salary; } public String getName() { return name; } public Long getSalary() { return salary; } } |
আমরা ক্লাসের ভ্যারিয়েবলগুলো প্রাইভেট ডিক্লেয়ার করি এবং সেটার (setter) ব্যবহার করে সেগুলোর মান আপডেট করি, গেটার (getter) ব্যবহার করে মানগুলো এক্সেস করি। এতে করে অবজেক্ট অরিয়েন্টেড প্রোগ্রামিং এর encapsulation প্রোপার্টি মেইনটেইন হয়, সরাসরি মান আপডেট করার থেকে এভাবে করা বেটার।
কিন্তু setter ব্যবহার করার বড় সমস্যা হলো ভ্যারিয়েবলগুলো মান যখন-তখন আপডেট করা যায়, ক্লাসটা আর ইমিউটেবল থাকে না। setter গুলো দূর করে দিয়ে ক্লাস ভ্যারিয়েবলগুলোকে ফাইনাল বানিয়ে দিলে আমাদের সমস্যা সমাধান হবে:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public final class Employee { final private String name; final private Long salary; public Employee(final String name, final Long salary) { this.name = name; this.salary = salary; } public String getName() { return name; } public Long getSalary() { return salary; } } |
এইবার এই employee অবজেক্টের মান আর কেও আপডেট করতে পারবে না (রিফ্লেকশন ব্যবহার করে এখনো আপডেট করা যাবে কিন্তু সে আলোচনায় যাচ্ছি না)। এখন আমাদের প্রথম কোডটার চেহারা হবে এরকম:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
Employee processBonus(final String userId) { final Employee employee = database.get(userId); final long salary = e.salary; //part A - 10 more lines final long salaryWithPerformanceBonus = salary + performanceBonus; //part B - 10 more lines final long finalSalaryWithAllBonus = salaryWithPerformanceBonus + eidBonus; //part C - 10 more lines final Employee employeeWithBonus = new Employee( employee.getName(), finalSalaryWithAllBonus ); //part D - 5 more lines return employeeWithBonus; } |
এখন আমরা নিশ্চিত থাকতে পারছি এই কোডে কোনো ভ্যারিয়েবলের মান পরির্তন হচ্ছে না।
সফটওয়্যার ইঞ্জিনিয়ারিং এ একটা কথা আমরা প্রায়ই বলি “There is no silver bullet”। এটার মানে হলো এমন কোনো পদ্ধতি নেই যেটা তোমার সব সমস্যার সমাধান করে দিবে, সব পদ্ধতিরই নেগেটিভ দিক আছে। যেমন ট্রান্সফরমেশনের জন্য নতুন একটা অবজেক্ট বা ভ্যারিয়েবল ডিক্লেয়ার করলে সেটা বাড়তি জায়গা নেয়। তবে বেশিভাগ সময় সেটা সমস্যা না কারণ অবজেক্টের আকার সাধারণ ছোটো হয় এবং লাইফসাইকেল শেষ হলে গার্বেজ কালেক্টর সেটাকে মেমরি থেকে সরিয়ে নেয়। তবে কোনো কারণে অবজেক্টটা অনেক বড় হলে নতুন অবজেক্ট ডিক্লেয়ার করার আগে দুইবার ভাবতে হবে।
জাভা বা অন্য যেকোনো ল্যাংগুয়েজে তুমি কাজ করো, তোমাকে শিখে নিতে হবে সেই ল্যাংগুয়েজে কিছু ইমিউটেবিলিটি মেইনটেইন করতে হয়। তবে অনেক ক্ষেত্রেই ইম্পারেটিভ পদ্ধতিতে তোমার ইমিউটেবিলিটি মেইনটেইন করতে সমস্যা হবে। যেমন যদি ফ্ল্যাগ বা কাউন্টার ব্যবহার করে কিছু করতে হয় তাহলে হয়তো count++ বা flag = true এর ধরণের কোড তোমাকে লিখতে হবে, সেক্ষেত্রে count বা flag আর কনস্টেন্ট থাকছে না। এটার সমাধান হলো ফাংশনাল প্রোগ্রামিং ব্যবহার করা। আজকাল অনেক ল্যাংগুয়েজেই ফাংশনাল প্রোগ্রামিং সাপোর্ট করে, জাভা ৮ এ ফাংশনাল প্রোগ্রামিং করা যায়, পাইথনে আরো আগে থেকে করা যায়। ফাংশনাল প্রোগ্রামিং তোমার কোডকে অনেক বেশি পরিচ্ছন্ন করে দিবে এবং বাগের সংখ্যা অনেকটাই কমে আসবে। সেটা নিয়ে বিস্তারিত হয়তো আরেকদিন আলোচনা করবো।
আজকের লেখাটা পড়ার পর তোমার কাজ হবে তোমার পুরো টিমকে ইমিউটেবলিটি সম্পর্কে জানানো এবং কোড রিভিউ করার সময় এটা এনফোর্স করা। এবং সময় পেলেই পুরোনো কোডগুলো রিফ্যাক্টর করা। আর সেই সাথে যদি ফাংশনাল প্রোগ্রামিং শিখে নিতে পারো তাহলে ২মাসের মধ্যেই দেখবে তোমার প্রজেক্টের কোড অনেক ইম্প্রুভ করেছে।
হ্যাপি কোডিং!
ফেসবুকে মন্তব্য
Powered by Facebook Comments
One small observation vaiya, in the last snippet of code, didn’t you mean to return employeeWithBonus?
Fixed, thanks.