স্পয়লার সতর্কতা: এই লেখায় স্পয়লার আছে গ্রেইন্স অনুশীলনী নিয়ে সাধারণভাবে, আর বিশেষ করে Bash ট্র্যাকের গ্রেইন্স অনুশীলনী নিয়ে। আপনি যদি এখনও এটি নিজে শেষ না করে থাকেন, আর কিছু সমাধান দেখতে না চান, তাহলে শেষ করে আবার ফিরে আসুন!
নতুন একটি কোম্পানিতে এটাই আপনার প্রথম দিন। কাগজপত্রের কাজ শেষ, দলের সঙ্গে দেখা হয়েছে, আর অবশেষে সময় এসেছে আপনি বসে সেই কোড পড়া শুরু করবেন যাতে আপনি কাজ করবেন। আপনি বিভিন্ন ফাংশন, ক্লাস আর মডিউল পড়তে শুরু করেন, আর পড়তে পড়তে দেখেন আপনি বিভ্রান্ত হয়ে স্ক্রিনের দিকে চোখ কুঁচকে তাকিয়ে আছেন। আপনি পড়তে থাকেন, আর আপনার মুখ থেকে একটি শব্দ বেরিয়ে আসে, প্রায় উচ্চারণই করা হয়নি, প্রায় নিঃশ্বাসে মিশে যায়: "কীssssssssss..."1 আপনি যত এগোন, তত এই ঘটনা বাড়তে থাকে, আপনি তত বেশি হতবুদ্ধি হন আর একটু রাগও করেন।
এই কোডে কী ঘটছে?
যখনই একই কোডে একের বেশি মানুষ কাজ করে, তখন জিনিসটা সহজে সামলাতে যতটা যত্ন আর সচেতনতা দরকার হয় তা অনেক বেড়ে যায়। এখন আর ব্যাপারটা শুধু আপনার মাথায় থাকা ধারণা আর সেই ধারণাকে বাস্তবে রূপ দেওয়ার কোড নয়। এখন ধারণাটাকে কোডের ভেতরেই থাকতে হবে, যেখানে সব সহকর্মী সেটি দেখতে পারেন আর দরকার হলে বদলাতে পারেন।
কোনো কিছু আপনি কীভাবে বাস্তবায়ন করবেন, সেটা ব্যবহারকারীর কাছে বিশেষ কোনো অর্থ বহন করে না, কিন্তু আপনার ডিজাইন একবার যে-ই ছুঁয়ে দেখেন, তাঁর কাছে সেটা অনেক কিছু বলে। একই কাজ করার জন্য প্রায়ই অনেকগুলো উপায় থাকে, আর মনে হতে পারে যেকোনো একটি বেছে নিলেই কাজ চলে যাবে। কিন্তু আমার বিশ্বাস, আপনার নেওয়া প্রতিটি সিদ্ধান্তের পেছনে একটা কারণ থাকা উচিত (ছোট সিদ্ধান্ত হলে ছোট কারণ হলেও চলবে), আর সেই কারণটির মধ্য দিয়ে কোনো লক্ষ্য বা প্রয়োজনীয়তা ফুটে ওঠা উচিত।
বাস্তবায়নের খুঁটিনাটি যেন কোড পড়েন যাঁরা, তাঁদের চিন্তাধারা, লক্ষ্য আর অগ্রাধিকার বুঝতে সাহায্য করে, এই ভাবনার নামই ডিজাইন ইনটেন্ট। আপনার ভ্যারিয়েবলগুলোর নাম কীভাবে দেন, আপনার ফাংশনের প্যারামিটার কী কী, আর জিনিসগুলো কীভাবে অ্যাবস্ট্রাক্ট করা হয়েছে, এসবই সেই জায়গা যেখানে ডিজাইন ইনটেন্ট প্রকাশ পায়, ভালোভাবেও, খারাপভাবেও।
আমি দৃঢ়ভাবে বিশ্বাস করি, একটি ইঞ্জিনিয়ারিং ডিজাইন বাস্তবায়নের সময় ডিজাইন ইনটেন্ট সবচেয়ে গুরুত্বপূর্ণ যেসব বিষয় মাথায় রাখতে হয় তার একটি। এটাই সফটওয়্যার ইঞ্জিনিয়ারিংকে প্রোগ্রামিং থেকে আলাদা করে দেয়।
সময় আর অন্যান্য প্রোগ্রামার যোগ করলে প্রোগ্রামিংয়ের যা হয়, সেটাই সফটওয়্যার ইঞ্জিনিয়ারিং।
ডিজাইন ইনটেন্ট সব ক্ষেত্রেই প্রযোজ্য
আমি একজন মেকানিক্যাল ইঞ্জিনিয়ার হিসেবে কাজ করি, মূলত চিকিৎসা যন্ত্রপাতির জন্য ইনজেকশন মোল্ড ডিজাইন করি। আমার সব ডিজাইন শেষ হওয়ার পর সোজা দরজা দিয়ে বেরিয়ে মেশিন শপে যায়, সেখানে তারা সব টুকরো বানাতে আর জোড়া লাগাতে শুরু করে। যেহেতু প্রতিটি ডিজাইন তৈরি করার সময় আমার মাথায় কী কী চলেছিল তা তারা জানে না, তাই নিজের উদ্দেশ্য ডিজাইনের মধ্য দিয়েই দেখানোর একটি উপায় আমাকে খুঁজে বের করতে হয়।
অনেক সময় কিছু ফিচার বিশেষভাবে গুরুত্বপূর্ণ হয়ে ওঠে। হয় গ্রাহক বলেছেন সেখানে তাঁদের বিশেষ টাইট টলারেন্স দরকার, নয়তো মোল্ড যেভাবে একসঙ্গে বসে তার জন্য কোনো কারণে অত্যন্ত নিখুঁত নির্ভুলতা লাগে। তাই গুরুত্বপূর্ণ অংশে নির্ভুলতাকে প্রাধান্য দেওয়ার মতো করে মেশিনিস্টদের টুকরো বানাতে সাহায্য করার জন্য আমাকে এমন কিছু জায়গা রেখে দিতে হয় যা স্পষ্টভাবে স্কোয়ার, বা ভাইসে একটা নির্দিষ্টভাবে সহজে বসানো যায়। এতে তাদের জন্য সবচেয়ে সহজ পথটাই আমার জন্য সবচেয়ে ভালো ফল দেয়।
আবার এমন জায়গাও আছে যেখানে মাপ এতটা গুরুত্বপূর্ণ নয়। যেমন, ডিজাইনে যদি শুধু বাতাস চলাচলের জন্য একটা ছিদ্র রাখি, তাহলে সেটা 6mm-এর মতো চেনা একটি কমন মাপের করে দিই।
এই ছিদ্রটি মেশিন করার সময় যখন তারা মাপতে যাবে যে এটা কেমন হলো, তখন 5.99mm-এর মতো একটা সংখ্যা দেখলে তারা ভাববে, "আচ্ছা, এটা সম্ভবত 6mm হওয়ার কথা ছিল, তাহলে আমি বেশ কাছাকাছিই আছি," আর তখন CAD বা স্পেসিফিকেশন ড্রয়িংয়ে মাপ মিলিয়ে দেখার দরকারই পড়বে না। কিন্তু উল্টোটা হলে, যদি আমি সেটাকে 5.87mm-এর মতো অচেনা কিছু বানাতাম, তাহলে তারা তাকিয়ে প্রথমেই এই প্রতিক্রিয়া দেখাত:
- ইশ, আমি কি খুব ছোট করে ফেলেছি? এটা 6mm হওয়ার কথা ছিল?
- (তারা গিয়ে CAD দেখে আর বুঝতে পারে তাদের ছিদ্র ঠিকই আছে, এটা শুধু একটা অচেনা মাপ।)
- হুমম... নিশ্চয়ই এই ছিদ্রের অচেনা মাপ হওয়ার পিছনে কোনো কারণ আছে। হয়তো এটা সত্যিই গুরুত্বপূর্ণ, নয়তো গ্রাহক এখানে বিশেষ একটি ছিদ্র চেয়েছেন। আমাকে Ryan-এর সঙ্গে কথা বলতে হবে, দেখতে হবে এই ছিদ্রের এমন কী গুরুত্ব।
- (ঠাস! তারা ভদ্রভাবে অ্যালুমিনিয়ামের টুকরোটা আমার ডেস্কে নামিয়ে রাখে।)
- (তারা জানতে পারে এই ছিদ্র নিয়ে আসলে কিছুই গুরুত্বপূর্ণ নেই, আমি শুধু একটা অদ্ভুত মাপ বেছে নিয়েছি, আর এত বাড়তি পরিশ্রম আর দুশ্চিন্তা সব বৃথা গেছে।)
- বাবা রে, এই Ryan লোকটা তো সত্যিই কম নয়! (বিড়বিড়, গালি, বিড়বিড়)
এসব কিছু হয় কারণ আমার ডিজাইনের প্রতিটি সিদ্ধান্ত, অন্য যারা সেটি দেখছেন আর নিয়ে কাজ করছেন তাঁদের কাছে কিছু না কিছু বার্তা পৌঁছে দেয়, আমি চাই বা না চাই। তাঁদের হতেই হবে এর মধ্যে অর্থ খুঁজে বের করতে, কারণ এটাই তাঁদের হাতে থাকা একমাত্র তথ্য! তাই আমার ডিজাইনে অর্থবহ, উদ্দেশ্যপূর্ণ তথ্য দেওয়ার সময় বের করতে পারলে সেটাই অনেক ভালো।
গ্রেইন্স: একটি পরিচিতি
এখন চলুন দেখি কোডে ডিজাইন ইনটেন্ট কীভাবে জানানো যায়, Exercism-এর একটি অনুশীলনীর উদাহরণ ধরে। সম্প্রতি একজন শিক্ষার্থীর সঙ্গে আমি Bash ট্র্যাকে তাদের গ্রেইন্স অনুশীলনীর সমাধান নিয়ে কাজ করেছি। গ্রেইন্স হলো এমন একটি অনুশীলনী যা গম আর দাবা বোর্ডের সমস্যাটি নিয়ে। সংক্ষেপে, দাবা বোর্ডের প্রথম বর্গে এক দানা গম রাখা হয়। পরের বর্গে দুই দানা। পরের বর্গে চার দানা। এভাবে চলতে থাকে, প্রতিটি বর্গে আগেরটির দ্বিগুণ দানা থাকে। শিক্ষার্থীদের বলা হয়, বোর্ডের প্রতিটি বর্গের আলাদা মান, আর সব মিলিয়ে মোট দানার সংখ্যা, দুটোই হিসাব করার একটি উপায় বের করতে।
এই শিক্ষার্থী মোট হিসাব করার বেশ চতুর একটি উপায় বের করেছিলেন।
bc <<< 'ibase=16;FFFFFFFFFFFFFFFF'
bc হলো একটি কমান্ড-লাইন ক্যালকুলেটর। আপনি এতে গাণিতিক অঙ্কের স্ট্রিং পাস করতে পারেন, আর সেটি সেগুলোর মান বের করে দেবে, খুব বড় ইন্টিজার আর ফ্লোটিং পয়েন্ট সংখ্যার ক্ষেত্রেও। Bash-এ bc ব্যবহার না করে হিসাব করার অন্য উপায়ও আছে, কিন্তু সহজ রাখার জন্য আমরা দেখব bc ব্যবহার করার সময় উদ্দেশ্য কীভাবে জানানো যায়, আর কীভাবে যায় না।
এই সমাধানটি কাজ করে কারণ পুরো অনুশীলনীটি দুইয়ের ঘাতকে ঘিরে আবর্তিত, আর যেখানে দুইয়ের ঘাত আছে, সেখানে আছে বাইনারি, আর যেখানে বাইনারি আছে, সেখানে আছে হেক্সাডেসিমাল2!
এটা চতুর সমাধান, কিন্তু কোডটা আমাদের কী বলছে? এই যে এখানে হেক্সাডেসিমাল গুরুত্বপূর্ণ? সমস্যাটা মূলত ১৬-কে ঘিরে আবর্তিত? সমস্যার বিবৃতিটি আরেকবার পড়ার পর স্পষ্টই বোঝা যায়, দুটোর কোনোটাই আসলে নয়। শিক্ষার্থী আর আমি মিলে নানা আইডিয়া ভাবলাম, কীভাবে উদ্দেশ্য আরও স্পষ্টভাবে জানানো যায়। আমাদের মাথায় যেগুলো এল:
প্রথম বিকল্প: বাইনারি
যেহেতু এখানে অনেক কিছুই দ্বিগুণ হতে থাকে (আর তাই অনেকগুলো ২-এর ঘাতও), চলুন দেখি বাইনারিতে আসলে কী ঘটছে, সেটা আমাদের কাজে লাগে কি না।
প্রথম বর্গে এক দানা আছে। বাইনারিতে এটিও হবে 0b1 (এখানে 0b শুধু বোঝায় "এটি একটি বাইনারি সংখ্যা", আসল সংখ্যাটি হলো 1)।
দ্বিতীয় বর্গে দুই দানা আছে। বাইনারিতে 0b10। এখন পর্যন্ত মোট ৩ (বা 0b11)।
তৃতীয় বর্গে ৪ দানা (0b100) আছে। এখন পর্যন্ত মোট: ৭ (0b111)।
চতুর্থ বর্গে ৮ দানা (0b1000) আছে। এখন পর্যন্ত মোট: ১৫ (0b1111)।
প্যাটার্নটা কি দেখতে পাচ্ছেন?
প্রতিটি বর্গ আরেকটি বাইনারি অঙ্ক বোঝায়, আর সবগুলো একসঙ্গে যোগ করলে শুধু একগুচ্ছ ১-এর সারি তৈরি হয়।
শিক্ষার্থীর সমাধানে আমরা F-গুলোকে ৬৪টি ১ দিয়ে বদলে দিতে পারি (প্রতিটি বর্গের জন্য একটি)!
bc <<< "ibase=2;1111111111111111111111111111111111111111111111111111111111111111"
আরও উদ্দেশ্যপূর্ণ, কারণ এটা সমস্যাটি আমাদের যা দেয় তার সঙ্গে আরও ভালো মেলে। কিন্তু আমরা তো রোবটের ভাষা বলি না। একের পর এক প্রায়-অগণন ১-এর লম্বা স্ট্রিং হয়তো কোনো উন্নতিই নয়।
দ্বিতীয় বিকল্প: ব্রুট-ফোর্স গণনা
ঠিক আছে, তাহলে হয়তো অদশমিক সংখ্যা পদ্ধতিগুলো একেবারে ছেড়ে দিই। হাতে একটি দাবা বোর্ডের দানার যোগফল যেভাবে বের করতাম, অর্থাৎ প্রতিটি বর্গের দানাগুলো গুনে, কোডটাকে তার সঙ্গে মিলিয়ে দিলে কেমন হয়?
total=0
current_grains=1
for square in {1..64}; do
total=$( bc <<< "$total + $current_grains" )
current_grains=$( bc <<< "$current_grains * 2" )
done
echo "$total"
এটা অনেক বেশি পড়ার মতো আর বোঝার মতো। কোডটা স্পষ্টই দেখায়, দাবা বোর্ডের বর্গের সংখ্যা একটা চালিকাশক্তি, ঠিক যেমন প্রতিটি বর্গে দ্বিগুণ হওয়ার প্রভাবও। আমার মনে হয়, শুরুতে দেওয়া সমাধানটির চেয়ে এটি ভালো।
তবে।
এটা ধীরগতির। লুপ চালানো, যোগ করা, আর বারবার বাইরের কোনো কমান্ড ডেকে আনা? সব মিলিয়ে রান-টাইম একটু ধীরগতির হয়ে যায়। এখন, এটা খুব বড় ব্যাপার? না। আপনি যদি Bash-এ এটা স্ক্রিপ্ট করছেন, তাহলে সম্ভবত আপনি আগেই মেনে নিয়েছেন স্পিড নিয়ে আপনার কোনো বাধা নেই। কিন্তু এটা আরও ভালো হতে পারত কি? হ্যাঁ।
তৃতীয় বিকল্প: সরাসরি গণনা
তাহলে, ইটারেশন ছাড়া আমরা এগুলো সব কীভাবে যোগ করব?
চলুন একই সমস্যার একটি ছোট সংস্করণ ভাবি: পাঁচ বর্গের একটি দাবা বোর্ড3।
পাঁচটি বর্গে নিম্নলিখিত সংখ্যক দানা থাকবে:
---------------------
| 1 | 2 | 4 | 8 |16 |
---------------------
আর এখানে মোট হবে: 1 + 2 + 4 + 8 + 16 = 31। হুম। 31 এখনো আমার কাছে কিছু স্পষ্টভাবে চিৎকার করে বলছে না। চলুন একটু বড় করি।
আচ্ছা, ছয় বর্গের দাবা বোর্ড হলে কেমন হয়? এবার হিসাব যোগ করতে সুবিধা হবে বলে প্রতিটি বর্গের নিচে চলমান যোগফল দেখাচ্ছি।
-------------------------
| 1 | 2 | 4 | 8 |16 |32 |
| | 3 | 7 |15 |31 |63 |
-------------------------
আর যোগফল: 1 + 2 + 4 + 8 + 16 + 32 = 63। হুমম... আসলে আমার একটা প্যাটার্নের আভাস পেতে শুরু করেছি, তবে নিশ্চিত হতে আরেকটা করে দেখি।
৭টি বর্গ:
-----------------------------
| 1 | 2 | 4 | 8 |16 |32 |64 |
| | 3 | 7 |15 |31 |63 |127|
-----------------------------
1 + 2 + 4 + 8 + 16 + 32 + 64 = 127। দেখতে পাচ্ছেন? 31, 63, 127 এই মানগুলো আপনার মনে কোনো ঘণ্টা বাজাচ্ছে?
এগুলো প্রায় ২-এর ঘাত। আসলে এগুলো পরের ২-এর ঘাত থেকে এক কম।
বিষয়টা ভালোভাবে বোঝাতে আরেকটি উদাহরণ। একটা ১২ বর্গের দাবা বোর্ড কল্পনা করুন। সেটা হলো এক, ১১ বার দ্বিগুণ (যা গণিতের ভাষায় 2^11): 2048। আবার দ্বিগুণ করলে পাবেন 4096 (2^12)। তাহলে... আমরা যদি প্যাটার্নটা ঠিক ধরেছি, তবে চলমান যোগফল হবে 4096-এর এক কম, অর্থাৎ 4095। আর গুনে দেখলে ঠিক সেটাই পাই: 1 + 2 + 4 + 8 + 16 + 32 + 64 + 128 + 256 + 512 + 1024 + 2048 = 4095।
অন্যভাবে বললে,
nসংখ্যক বর্গের মোট যোগফল বের করতে হলে ২-এর ঘাত এক ধাপ বাড়াতে হবে আর ফলাফল থেকে ১ বিয়োগ করতে হবে।
৬৪ নম্বর বর্গে দানার সংখ্যা 2^63 (মনে আছে, জিরো-ইনডেক্সিং?)। তাহলে, ৬৪ নম্বর বর্গ পর্যন্ত সব বর্গের মোট দানা হিসাব করতে হলে আমাদের 2^64 হিসাব করে ১ বিয়োগ করতে হবে।
ধুম!
Bash-এ এটা দেখতে হবে এমন:
bc <<< "2^64 - 1"
বাইনারিতে কী ঘটছে তা নিশ্চিত করলে এটা বোঝা যায়। বাইনারিতে ৬৪টি বর্গের মোট কত ছিল?
0b1111... # 64 ones
তাত্ত্বিকভাবে ৬৫তম বর্গে দানার সংখ্যা কত?
0b10000... # 1 and 64 zeros
১ আর ৬৪টি শূন্য থেকে ৬৪টি ১-এ কীভাবে যাবেন? ১ বিয়োগ করলেই হবে।
আর এতে আমাদের অতিরিক্ত কী লাভ? এখন মোট যোগফলের জন্য আমাদের হাতে একটা সুন্দর, পড়ার মতো এক্সপ্রেশন আছে। এটা ইটারেশন করে না, তাই পারফরম্যান্স ভালো। আর এর মধ্যে ৬৪ সংখ্যাটি আছে, যা দাবা বোর্ডের বর্গের সংখ্যা, আর এটা সুন্দরভাবে সংকেত দেওয়া ডিজাইন ইনটেন্ট-এর ভালো একটি উদাহরণ। যদি কোনো কারণে ১০০০ বছর পর বিশ্বে ৭x৭ দাবা বোর্ডের মান হয়ে যায়, তাহলে সেই ভবিষ্যৎ ইঞ্জিনিয়ার (সম্ভবত Bash 6.1 ব্যবহার করবেন) স্ক্রিপ্টটা দেখে বুঝে নেবেন আপনি কী চেয়েছিলেন, আর ৬৪-কে বদলে ৪৯ করে দেবেন। সব ঠিক!
উদ্দেশ্যপূর্ণ থাকুন, বন্ধুরা
কোনো কিছু বাস্তবায়নের কাজ করতে গেলে এলোমেলোভাবে জিনিসপত্র ছুড়ে ফেলে প্রথম যে সমাধানটা কাজ করে তার উপর আঁকড়ে ধরা সহজ। সমস্যাটা খুঁজে বের করার সময় এটা ঠিক আছে, কিন্তু গুরুত্বপূর্ণ অংশগুলো আপনি পুরোপুরি বুঝে ফেলার পর, যদি জিনিসগুলো ভালোভাবে ঝালিয়ে নেওয়ার সময় আপনার হাতে থাকে, তাহলে নিশ্চিত করুন যেন প্রতিটি অ্যালগরিদম, প্রতিটি ভ্যারিয়েবলের নাম, এমনকি আপনার হোয়াইট স্পেসও সমস্যাটার, গুরুত্বপূর্ণ প্রয়োজনীয়তাগুলোর, আর সব টুকরো কীভাবে একসঙ্গে বসে তার একটি ছবি এঁকে দেয়।
-
এছাড়া Thom Holwerda-এর কমিক দেখুন। ↩
-
বাইনারি আর হেক্সাডেসিমাল গোনায় যদি একটু মরচে পড়েছে মনে হয়, তাহলে @kytrinyx How to Count বইটি সুপারিশ করেন। আর লজ্জাহীন আত্মপ্রচারে বলি, আমি সম্প্রতি বাইনারি আর হেক্সাডেসিমাল নিয়ে কয়েকটি ব্লগ পোস্টও লিখেছি। ↩
-
সেটা কীভাবে কাজ করবে আমি জানি না। হয়তো আমরা বোড়েগুলোকে একে অপরের সঙ্গে বর্শা-যুদ্ধ করাতেই পারি। ↩