Duplicate Code: ৫০টা বিয়ের কার্ডে হাতে লেখা একই ঠিকানা
বিয়ের কার্ডের গল্প দিয়ে Duplicate Code smell বোঝো। DRY, Rule of Three, আর Extract Method দিয়ে copy-paste কোডের বিপদ থেকে বাঁচো।
পঞ্চাশটা বিয়ের কার্ড, একটা ব্যথা হাত
ধরো রাহেলার পরিবারে বড় খবর — রাহেলা আপার বিয়ে হচ্ছে! পুরো বাড়িতে মিষ্টির গন্ধ, তিনটা জেলা থেকে আত্মীয়রা ফোন করছে। প্রিন্টার থেকে কার্ড এসেছে — বড় কার্টনে সোনালি সুতায় বাঁধা, দেখতে অসাধারণ।
কিন্তু বাবা একটা কার্ড খুলে দুবার পড়লেন, মুখ কালো হয়ে গেল। প্রিন্টার venue-এর ঠিকানা ছাপতেই ভুলে গেছে। পঞ্চাশটা কার্ড, একটাতেও লেখা নেই বিয়েটা কোথায়।
Reprint করার সময় নেই। তাই রাহেলা আপার ছোট ভাই কবির — চৌদ্দ বছর বয়স, সুন্দর হাতের লেখার জন্য বিখ্যাত — কাজটা পেল। "প্রতিটা কার্ডে ঠিকানা লেখো," বাবা বললেন। "পঞ্চাশটাতেই। সুন্দর করে।"
কবির ডাইনিং টেবিলে বসল নীল কলম নিয়ে। "মঙ্গল কমিউনিটি সেন্টার, রোড ১৪, ধানমন্ডি, ঢাকা — ১২০৫।" একবার লিখল। দারুণ। দশবার। হাত ব্যথা করছে। বিশতম কার্ডে ছোট বোন কার্টুন দেখছে, কবির যোগ দিতে চাইছে। ত্রিশের পর লেখা হেলে যাচ্ছে। চৌত্রিশতম কার্ডে লিখল "রোড ৪১" — "রোড ১৪"-এর বদলে। বিয়াল্লিশতম কার্ডে পোস্ট কোডই ভুলে গেল। কেউ ধরল না। পঞ্চাশটা কার্ড একে একে কে পড়বে?
দুই সপ্তাহ পর জামাল চাচার পরিবার রোড ৪১-এ পৌঁছাল — পুরো বিয়ের পোশাক পরে। সেখানে একটা খালি জায়গায় একটা কুকুর ঘুমাচ্ছে। তারা বাবাকে ফোন করলেন। বাবা এগারোবার মাফ চাইলেন।
আর তারপর সবচেয়ে খারাপ খবর: venue পরিবর্তন হয়ে গেছে। মঙ্গল কমিউনিটি সেন্টার double-book হয়েছে, বিয়ে সরে গেছে "আনন্দ কমিউনিটি হল, মিরপুর রোড"-এ। এখন পঞ্চাশটা কার্ড খুঁজে বের করতে হবে — অর্ধেক তো পোস্ট হয়ে গেছে! পরিবারটা শেষমেশ প্রতিটা অতিথিকে একে একে ফোন করল, তারপরও দুটো পরিবার পুরনো venue-এ চলে গেল।
এবার ভাবো — প্রিন্টারের প্রথম থেকে কী করা উচিত ছিল। ঠিকানাটা এক জায়গায় রাখা — printing plate-এ। সেখান থেকে পঞ্চাশটা কার্ড stamp করা। Plate-এ একটা সংশোধন, পঞ্চাশটা কার্ড ঠিক। কোনো ব্যথার হাত নেই, কোনো ভুল রোড নেই, কাউকে ফোন করতে হচ্ছে না।
এটাই হলো Duplicate Code smell। একই জিনিস অনেক জায়গায় হাতে লেখা সহজ মনে হয়, কিন্তু প্রতিটা কপি ভুলের সুযোগ। আর ভবিষ্যতে যেকোনো পরিবর্তনে প্রতিটা কপি খুঁজে বের করতে হয়।
এই smell কী আসলে?
Duplicate Code হলো একই ধারণা প্রোগ্রামের একাধিক জায়গায় প্রকাশ পাওয়া। হুবহু copy-paste হতে পারে, বা দুটো block একটু আলাদা দেখালেও একই নিয়ম বহন করে। সেই নিয়ম পরিবর্তন হলে প্রতিটা কপি খুঁজে সঠিকভাবে বদলাতে হবে — নাহলে কপিগুলো নিজেদের মধ্যে দ্বিমত করে।
Duplicate Code বিখ্যাত কারণটা হলো — এটা Martin Fowler-এর Refactoring বইয়ে একদম প্রথম smell। Fowler আর Kent Beck এটাকে প্রথমে রেখেছেন কারণ এটা সবচেয়ে সাধারণ আর সবচেয়ে ক্ষতিকর।
এর সমাধান জুড়িয়ে আছে একটা বিখ্যাত নীতির সাথে — Andy Hunt আর Dave Thomas-এর The Pragmatic Programmer বইয়ের DRY — Don't Repeat Yourself। DRY বলে যে একটা সিস্টেমে প্রতিটা জ্ঞানের একটাই নির্ভরযোগ্য ঘর থাকবে। Venue-এর ঠিকানা একটা জ্ঞান। সেটা একটা printing plate-এ থাকবে, পঞ্চাশটা হাতলেখা কপিতে নয়।
DRY ভালো করে পড়ো — এটা জ্ঞান-এর কথা বলছে, text-এর নয়। দুটো block দেখতে একই কিন্তু আলাদা business নিয়মের জন্য — সেগুলো আসল duplication নয়। আজকে শুধু দুর্ঘটনাক্রমে একরকম দেখাচ্ছে। আসল duplication হলো এক নিয়ম অনেক জায়গায় বাস করা। নিয়ম merge করো, দেখতে একরকম জিনিস না।
একটু গভীরে যাই: Code clone নিয়ে গবেষণায় এই smell-এর formal vocabulary আছে। Type-1 clone হলো হুবহু কপি (whitespace বাদে)। Type-2 identifier rename করে কিন্তু structure রাখে। Type-3 কয়েকটা statement যোগ বা বাদ দেয়। Type-4 হলো semantically equivalent কোড কিন্তু ভিন্ন syntax — একই কাজ ভিন্ন উপায়ে। Clone-detection tool (PMD CPD, SonarQube, jscpd, Simian) Type 1-2 তে ভালো কাজ করে, Type 3-এ দুর্বল, আর Type 4 প্রায় দেখতেই পায় না। এই asymmetry-টা গুরুত্বপূর্ণ কারণ tool যে clone ধরতে পারে না সেগুলো শুধু অর্থ বোঝা মানুষ ধরতে পারে। Industrial codebase-এ নিয়মিত ৫-২০% cloned code পাওয়া যায়, আর clone group-এ inconsistent edit defect-এর উল্লেখযোগ্য কারণ।
পুরো বিষয়টা এক মানচিত্রে দেখো:
কীভাবে চিনবে
যেকোনো codebase-এ এই checklist চালাও:
- দুটো method প্রায় একই — শুধু একটা সংখ্যা, একটা type, বা একটা called method আলাদা।
- একই parent-এর একাধিক subclass-এ একই sequence of statements।
- একটা bug "fix" হয়েছিল কিন্তু আবার দেখা দিল — কারণ buggy কোডের আরেকটা কপি কখনো fix হয়নি।
- Team-এর অভ্যাস "existing handler কপি করে দুই লাইন পরিবর্তন করলাম" প্রতিটা নতুন feature-এ।
- একই constant, regex pattern, বা formula অনেক file-এ হাতে টাইপ করা।
- আলাদা file-এ parallel
if/switchladder যেগুলো একই set of cases তালিকাভুক্ত করে।
এখানে duplication-এর ধরনের একটা table, সহজ থেকে কঠিন পর্যন্ত:
| ধরন | কেমন দেখায় | কতটা দৃশ্যমান? | উদাহরণ |
|---|---|---|---|
| হুবহু কপি | অক্ষর অক্ষর একই লাইন | সহজ — tool ধরে | Copy-paste করা validation block |
| Renamed variable সহ কপি | একই logic, ভিন্ন নাম | মাঝারি | total/sum, cust/customer |
| একই steps, ভিন্ন details | একই skeleton, একটা step আলাদা | কঠিন | দেশি বনাম আন্তর্জাতিক billing |
| একই কাজ, ভিন্ন algorithm | একটা উত্তর দুইভাবে গণনা | খুব কঠিন | এক file-এ loop, অন্যটায় formula |
| Knowledge duplication | একটা নিয়ম code, config আর docs-এ | সবচেয়ে কঠিন | তিন layer-এ VAT rate |
নিচের সারিগুলো বিপজ্জনক — কোনো tool এগুলো পুরোপুরি ধরতে পারে না। শুধু অর্থ বোঝা reader বলতে পারবে, "দেখো, এই দুটো block আসলে একই নিয়ম।"
Team যখন audit করে duplication কোথা থেকে এলো, সাধারণত এভাবে ভাগ হয়:
কেন এটা সমস্যা
সমস্যা ১: প্রতিটা পরিবর্তন গুণিতক হয়ে যায়। এক জায়গার নিয়ম একবার পরিবর্তন করতে হয়। পাঁচ জায়গায় কপি করা নিয়ম পাঁচবার খুঁজে পাঁচবার edit করতে হয়। একটা মিস করলে প্রোগ্রাম একসাথে দুটো ভিন্ন নিয়ম মানছে — যেন পঞ্চাশটা কার্ডে দুটো ভিন্ন venue।
সমস্যা ২: কপিগুলো নিঃশব্দে দ্বিমত করে। কেউ ঘোষণা করে না "কপি তিন এখন আলাদা!" গরমিল লুকিয়ে থাকে যতক্ষণ না কোনো customer এতে পড়ে। জামাল চাচা রোড ৪১-এ পৌঁছানোর কথা মনে করো — তিনি production-এ bug আবিষ্কার করলেন, বিয়ের পোশাক পরে।
সমস্যা ৩: Bug পুনরুজ্জীবিত হয়। একটা কপিতে bug fix করলে ticket বন্ধ। মাস পরে আরেকটা কপির মাধ্যমে একই bug ফিরে আসে। Team মনে করে fix "কাজ করেনি।" বিশ্বাস কমে। দেখো এটা কীভাবে হয়:
সমস্যা ৪: Design অদৃশ্য হয়ে যায়। একটা concept একবার নাম পেলে আর reuse হলে, design নিজেই বলে দেয়: "এটা subtotal নিয়ম।" কপিতে ছড়িয়ে গেলে প্রতিটা reader-কে পুনরাবিষ্কার করতে হয় এই blocks আসলে একই জিনিস। প্রতিদিন, প্রতিটা reader-এর মানসিক শক্তির অপচয়।
সমস্যা ৫: খরচ প্রতিটা কপির সাথে বাড়তে থাকে। ব্যথাটা linear নয় — বেশি কপি মানে সেগুলো খুঁজতে, edit করতে, test করতে, আর কোনোটা বাদ পড়েনি নিশ্চিত করতে সময় লাগে:
আর এখানে একটা paste করা block-এর ধীরে ধীরে জীবনের গল্প। লক্ষ্য করো drift কখনো দরজায় কড়া নাড়ে না — শুধু হয়ে যায়:
বাস্তব কোডের উদাহরণ
বিয়ের কার্ডের গল্পটা এবার কোডে রাখি। ধরো পরিবারটা invitation print আর পাঠানোর জন্য একটা event app বানিয়েছে। একজন junior developer লিখেছে — অবশ্যই copy-paste করে।
// Smelly version: the "address rule" is hand-written in three places
class InvitationService {
printCard(guest: Guest): string {
const name = guest.title + " " + guest.firstName + " " + guest.lastName;
return (
"Dear " + name + ",\n" +
"You are invited!\n" +
"Venue: Shubham Garden, Plot 14, Tonk Road, Jaipur - 302018"
);
}
sendWhatsApp(guest: Guest): string {
const name = guest.title + " " + guest.firstName + " " + guest.lastName;
return (
"Namaste " + name + "! Wedding invitation: " +
"Venue: Shubham Garden, Plot 14, Tonk Rd, Jaipur 302018"
);
}
sendEmail(guest: Guest): string {
const name = guest.title + " " + guest.firstName + " " + guest.lastName;
return (
"Dear " + name + ", you are cordially invited. " +
"Venue: Shubham Gardens, Plot 14, Tonk Road, Jaipur - 302018"
);
}
}ভালো করে দেখো। Smell সর্বত্র:
- Guest name formula (
title + first + last) তিনবার লেখা। পরিবার যদি প্রতিটা নামের পর "ভাই/আপা" যোগ করতে চায়, তাহলে তিনটা edit। - Venue address তিনবার লেখা — আর কপিগুলো ইতিমধ্যেই দ্বিমত করছে! WhatsApp version বলছে "Tonk Rd", email বলছে "Shubham Gardens"। কপিগুলো drift করেছে, ঠিক কবিরের চৌত্রিশতম কার্ডে ক্লান্ত হাতের লেখার মতো।
- Venue পরিবর্তন হলে কাউকে তিনটাই খুঁজে fix করতে হবে — আর অন্য কোনো file-এ লুকানো চতুর্থ কপিও।
ধাপে ধাপে পরিষ্কার করা
ধাপ ১: জ্ঞান খুঁজে বের করো। জিজ্ঞেস করো: এখানে কোন জ্ঞানের অংশগুলো পুনরাবৃত্তি হচ্ছে? দুটো আছে: "একজন guest-এর পুরো নাম কীভাবে লিখতে হয়" আর "venue address কী"।
ধাপ ২: প্রতিটা অংশের একটা ঘর দাও। Name formula-র জন্য Extract Method ব্যবহার করো, address-এর জন্য একটা constant। এটাই printing plate।
ধাপ ৩: প্রতিটা caller-কে সেই এক ঘর ব্যবহার করাও। প্রতিটা হাতলেখা কপিকে একটা call দিয়ে replace করো।
// Clean version: one printing plate, many stamps
const VENUE_ADDRESS = "Shubham Garden, Plot 14, Tonk Road, Jaipur - 302018";
class InvitationService {
printCard(guest: Guest): string {
return `Dear ${this.fullName(guest)},\nYou are invited!\nVenue: ${VENUE_ADDRESS}`;
}
sendWhatsApp(guest: Guest): string {
return `Namaste ${this.fullName(guest)}! Wedding invitation: Venue: ${VENUE_ADDRESS}`;
}
sendEmail(guest: Guest): string {
return `Dear ${this.fullName(guest)}, you are cordially invited. Venue: ${VENUE_ADDRESS}`;
}
private fullName(guest: Guest): string {
return `${guest.title} ${guest.firstName} ${guest.lastName}`;
}
}এখন venue পরিবর্তন একটা edit। Name নিয়ম একটা edit। কপিগুলো physically drift করতে পারে না, কারণ কপি নেই — শুধু একটা plate আর অনেক stamp। পরিষ্কার structure এরকম দেখায়:
ধাপ ৪: কঠিন duplication-এর জন্য সঠিক tool বেছে নাও। আমাদের উদাহরণ এক class-এর ভেতরে ছিল, তাই Extract Method যথেষ্ট ছিল। কিন্তু duplication অন্য জায়গায়ও থাকে, আর প্রতিটা জায়গার নিজস্ব সমাধান আছে:
- Sibling subclass-এ একই method? Method-টা parent-এ তুলে দাও Pull Up Method দিয়ে।
- Subclass method-এ একই steps কিন্তু ভিন্ন details? Parent-এ skeleton রাখো আর children-কে ভিন্ন steps পূরণ করতে দাও Form Template Method দিয়ে।
- Unrelated class-এ ছড়িয়ে পড়া কপি যেগুলো গোপনে একটা shared concept বহন করছে? সেই concept-কে নিজের ঘর দাও Extract Class দিয়ে।
- দুটো block একই কাজ ভিন্নভাবে করছে? পরিষ্কার পথটা বেছে নাও আর দুটোকে Substitute Algorithm দিয়ে replace করো।
C#-এ একই smell
ধরো দুটো billing method — একদম pair, শুধু একটা সংখ্যা আলাদা:
// Before: the subtotal loop is duplicated; only the rate differs
public decimal DomesticTotal(List<Item> items)
{
decimal subtotal = 0;
foreach (var i in items) subtotal += i.Price * i.Quantity;
return subtotal + subtotal * 0.05m; // domestic shipping
}
public decimal InternationalTotal(List<Item> items)
{
decimal subtotal = 0;
foreach (var i in items) subtotal += i.Price * i.Quantity;
return subtotal + subtotal * 0.18m; // international shipping
}Shared shape বের করো, পার্থক্যটা parameter হয়ে যাক:
// After: one definition of subtotal, one definition of shipping
public decimal DomesticTotal(List<Item> items) => TotalWithShipping(items, 0.05m);
public decimal InternationalTotal(List<Item> items) => TotalWithShipping(items, 0.18m);
private static decimal TotalWithShipping(List<Item> items, decimal shippingRate)
{
var subtotal = items.Sum(i => i.Price * i.Quantity);
return subtotal + subtotal * shippingRate;
}এখন "subtotal কীভাবে গণনা করা হয়" ঠিক একবার আছে। আগামীকাল business বললে "শূন্য quantity-র item বাদ দাও" — সেটা একটা edit, আর domestic ও international কখনো এটা নিয়ে দ্বিমত করতে পারবে না।
Python-এও একই ওষুধের একটু স্বাদ, কারণ copy-paste সব ভাষায় কথা বলে:
# Before: the same "clean phone number" rule, typed twice
def save_guest(name, phone):
phone = phone.replace(" ", "").replace("-", "")[-10:]
db.guests.insert(name, phone)
def send_invite_sms(phone, text):
phone = phone.replace(" ", "").replace("-", "")[-10:]
sms.send(phone, text)
# After: one rule, one home
def normalize_phone(phone: str) -> str:
return phone.replace(" ", "").replace("-", "")[-10:]
def save_guest(name, phone):
db.guests.insert(name, normalize_phone(phone))
def send_invite_sms(phone, text):
sms.send(normalize_phone(phone), text)Real project-এ এই smell কোথায় লুকায়
- Frontend আর backend-এ copy করা validation নিয়ম। Email regex React form-এ আর API controller-এ আছে — প্রতিটায় একটু আলাদা। User screen-এ "valid" দেখে কিন্তু server থেকে "invalid" পায়।
- Copy-paste driven feature development। "নতুন report বানাতে হবে? পুরনো report handler কপি করে adjust করো।" দশটা report-এর পর shared logic-এ একটা bug-এর জন্য দশটা fix দরকার।
- কোড আর SQL-এ একই formula। Application-এ discount গণনা আর database view-এ পুনরায় গণনা। এগুলো drift করে। Finance বছরের শেষে ধরে।
- Test কোড duplication। বিশটা test প্রতিটায় হাতে একই পাঁচ লাইনের test order তৈরি করছে। একটা constructor পরিবর্তনে বিশটাই ভাঙে।
- Cross-team duplication। দুটো team একই মাসে নিজেদের "retry helper" লিখল কারণ কেউ জানত না অপর team এটা করছে। Code search আর shared library-ই সমাধান।
- AI-generated কোড। Code assistant বিদ্যমান helper খোঁজার বদলে সানন্দে logic-এর একটা তাজা কপি generate করে। Generated কোড human কোডের মতোই duplication-এর জন্য review করো।
একটু গভীরে যাই: The Pragmatic Programmer থেকে একটা গভীর systems argument আছে: DRY violation লেখকরা যাকে "single source of truth" property বলেন তা ভাঙে, আর failure mode হলো representational drift — একটা fact-এর দুটো representation স্বাধীনভাবে এগিয়ে যাচ্ছে। এটা cache invalidation, denormalized database, আর documentation rot-এর মতো একই root সমস্যা। যখন ইচ্ছাকৃতভাবে knowledge duplicate করো (performance-এর জন্য, deployment decoupling-এর জন্য, offline copy-র জন্য), তখন একটা synchronization mechanismও তৈরি করতে হবে। Synchronization ছাড়া duplication হলো time bomb। Synchronization সহ duplication একটা engineering decision।
কখন উপেক্ষা করা ঠিক আছে
এখানে সৎ কথাটা বলি। প্রতিটা পুনরাবৃত্তি merge করা উচিত নয়। খুব তাড়াতাড়ি merge করলে ভিন্ন একটা রোগ হয়: ভুল abstraction।
| পরিস্থিতি | কপি merge করবে? | কেন |
|---|---|---|
| একই business নিয়ম, copy করা | হ্যাঁ | এক নিয়মের এক ঘর থাকতে হবে |
| দেখতে একরকম, কিন্তু ভিন্ন কারণে পরিবর্তন হয় | না | Incidental duplication; merge করলে অপরিচিতরা জুড়ে যায় |
| দ্বিতীয় occurrence, shape এখনো অস্পষ্ট | অপেক্ষা করো | Rule of Three: তৃতীয় কপিতে refactor |
| এক বা দুই জায়গায় দুই লাইনের fragment | সাধারণত না | একটা ছোট helper indirection যোগ করে, কম সরায় |
| পাঠযোগ্যতার জন্য পুনরাবৃত্ত test | প্রায়ই না | একটা test এক স্ক্রিনে একা পড়া যাওয়া উচিত |
| অনেক file-এ একই constant | হ্যাঁ | Constant centralize করা সস্তা, drift ব্যয়বহুল |
দুটো বিখ্যাত guideline বিচার করতে সাহায্য করে:
- The Rule of Three (Fowler-এর Refactoring-এ পরিচিত, Don Roberts-কে credit): একবার লেখো। একবার কপি করো, শুধু মনে রাখো। তৃতীয়বার দরকার হলে — refactor করো। ততক্ষণে তোমার কাছে তিনটা real example আছে, আসল shared shape অনুমান না করে দেখতে পারছো।
- Sandi Metz-এর সতর্কতা: "ভুল abstraction-এর চেয়ে duplication অনেক সস্তা।" দুটো block merge করলে যেগুলো শুধু দুর্ঘটনাক্রমে একরকম ছিল, পরে shared কোডে flags আর parameters thread করতে হবে সেগুলো আলাদা করতে। সেই জট পাকানো "shared" কোড honest copy-র চেয়ে অনেক খারাপ।
যেকোনো সন্দেহজনক জোড়া code block এই chart-এ রাখো আর decision পড়ো:
দুটো similar block merge করার আগে একটাই প্রশ্ন করো: "Business যদি এর একটা পরিবর্তন করে, তাহলে কি অন্যটাও পরিবর্তন করতে হবে?" হ্যাঁ হলে — একই জ্ঞান, merge করো। না হলে — তারা অপরিচিত যারা আজকে একরকম পোশাক পরেছে। তাদের আলাদা থাকতে দাও, এতে দোষী বোধ করো না।
কোন refactoring-গুলো এটা সারায়
| Duplication কোথায় | সমাধান refactoring |
|---|---|
| এক class-এর ভেতরে | Extract Method |
| Sibling subclass-এ একই method | Pull Up Method |
| Subclass-এ একই steps, ভিন্ন details | Form Template Method |
| Unrelated class-এ ছড়িয়ে | Extract Class |
| একই কাজ দুইভাবে | Substitute Algorithm |
| Pasted block থেকে বড় হওয়া long method | Extract Method + Consolidate Duplicate Conditional Fragments |
দ্রুত revision box
+--------------------------------------------------------------+
| DUPLICATE CODE — QUICK REVISION |
+--------------------------------------------------------------+
| Story : Hand-writing one address on 50 wedding cards |
| instead of printing from one plate. |
| Smell : The same KNOWLEDGE living in many places. |
| Danger : Every change -> many edits; missed copy -> |
| silent disagreement -> bug found by a customer. |
| DRY : Every piece of knowledge has ONE home. |
| (The Pragmatic Programmer) |
| Rule of : 1st time write, 2nd time wince, |
| Three 3rd time refactor. |
| Caution : Lookalikes that change for different reasons |
| are NOT duplication. Wrong abstraction > copies. |
| Cures : Extract Method, Pull Up Method, Form Template |
| Method, Extract Class, Substitute Algorithm. |
+--------------------------------------------------------------+অনুশীলন
একটা school-এর fee program copy-paste করে বড় হয়েছে। এটা পরিষ্কার করো।
function tuitionFeeReceipt(student: Student): string {
let fee = 2000;
if (student.hasSibling) fee = fee - fee * 0.1; // sibling discount
if (student.isStaffChild) fee = fee - fee * 0.5; // staff discount
return "Receipt for " + student.name + ": Rs " + fee + " (Tuition)";
}
function busFeeReceipt(student: Student): string {
let fee = 800;
if (student.hasSibling) fee = fee - fee * 0.1;
if (student.isStaffChild) fee = fee - fee * 0.5;
return "Receipt for " + student.name + ": Rs " + fee + " (Bus)";
}
function labFeeReceipt(student: Student): string {
let fee = 500;
if (student.hasSibling) fee = fee - fee * 0.1;
if (student.isStaffChild) fee = fee - fee * 0.45; // <-- bug? or rule?
return "Receipt for " + student.name + ": Rs " + fee + " (Lab)";
}তোমার কাজ:
- কোন জ্ঞানের অংশগুলো duplicate হয়েছে তা list করো। (Hint: অন্তত দুটো আছে — discount নিয়ম আর receipt format।)
- একটা
applyDiscounts(fee, student)function আর একটাformatReceipt(name, fee, feeType)function extract করো। তিনটা receipt function-কে এগুলো ব্যবহার করে one-liner হিসেবে পুনরায় লেখো। labFeeReceipt-এ0.45investigate করো। এটা কি একটা typo যেটা drift করেছে, নাকি একটা real special নিয়ম? প্রতিটা সম্ভাবনার জন্য একটা বাক্য লেখো, আর প্রতিটা ক্ষেত্রে কী করবে explain করো। এটাই চিত্র ৬-এর "copies disagree silently" সমস্যা।- School একটা চতুর্থ fee যোগ করছে: library fee, ৩০০ টাকা, একই discount। এটা যোগ করো। কত লাইন লাগল গুনো। Copy-paste style-এ কত লাইন লাগত তার সাথে তুলনা করো — তারপর চিত্র ৫-এর cost curve-এর সাথে তোমার সংখ্যা মেলাও।
- Bonus: একজন সহপাঠী পরামর্শ দেয় অন্য একটা school-এর program থেকে
tuitionFeeReceiptmerge করতে কারণ "দেখতে একই লাগছে।" Rule of Three, wrong-abstraction warning, আর চিত্র ১০-এর quadrant chart ব্যবহার করে explain করো এটা কি আসল duplication নাকি incidental lookalike।
তোমার final version যদি sibling discount ঠিক এক জায়গায় পরিবর্তন করে, তাহলে তুমি DRY badge অর্জন করেছো।
সচরাচর জিজ্ঞাসা
- সহজ ভাষায় duplicate code কী?
- Duplicate code মানে হলো একই জিনিস প্রোগ্রামের একাধিক জায়গায় লেখা। হুবহু copy-paste হতে পারে, আবার দুটো block একটু আলাদা দেখালেও একই কাজ করতে পারে। বিপদটা হলো ভবিষ্যতে যেকোনো পরিবর্তন প্রতিটা কপিতে করতে হবে — একটা বাদ পড়লেই bug।
- DRY principle কী?
- DRY মানে Don't Repeat Yourself। Andy Hunt আর Dave Thomas-এর The Pragmatic Programmer বই থেকে এসেছে। মানে হলো একটা সিস্টেমে প্রতিটা জ্ঞানের একটাই নির্ভরযোগ্য জায়গা থাকবে। নিয়ম এক জায়গায় থাকলে একবার পরিবর্তন করলেই সব ঠিক — নিজের সাথে কখনো মতবিরোধ হয় না।
- Rule of Three কী?
- Martin Fowler-এর Refactoring বইয়ে পরিচিত হওয়া একটা practical guideline: প্রথমবার লেখো, দ্বিতীয়বার দেখে মনে রাখো, তৃতীয়বার দেখলে refactor করো। তৃতীয়বারে আসল shared shape স্পষ্ট হয়ে যায়, তাই যে abstraction বের করো সেটা সঠিক হওয়ার সম্ভাবনা বেশি।
- একই রকম দেখতে সব কোডই কি duplication?
- না। দুটো কোড আজকে একরকম দেখালেও আলাদা business কারণে পরিবর্তন হয় — এগুলো শুধু দুর্ঘটনাক্রমে একরকম। একে incidental duplication বলে। এগুলো মেলালে সম্পর্কহীন নিয়ম জুড়ে যায়, পরে আলাদা করতে flags আর parameters লাগে। ভুল abstraction-এর চেয়ে duplication অনেক সস্তা।
- কোন refactoring-গুলো duplicate code দূর করে?
- এক class-এর ভেতরে কপির জন্য Extract Method, sibling subclass-এ একই method-এর জন্য Pull Up Method, steps একই কিন্তু details আলাদা হলে Form Template Method, ছড়িয়ে পড়া কপি একটা shared concept লুকালে Extract Class, আর দুটো আলাদা দেখতে block একই কাজ করলে Substitute Algorithm।
আরো দেখো
সম্পর্কিত পাঠ
Long Method: যখন একটা function সব কিছু করতে চায়
Long Method code smell শিখো সহজ গল্পের মাধ্যমে — TypeScript আর C# example সহ, Extract Method দিয়ে step-by-step refactoring। একদম beginner-friendly গাইড।
Shotgun Surgery: এক জায়গায় পরিবর্তন, দশ জায়গায় দৌড়াদৌড়ি
Shotgun Surgery code smell শিখবে রুবেলের বাসা বদলের গল্পের মাধ্যমে — সহজ সংজ্ঞা, TypeScript আর C# এর example, Divergent Change এর সাথে পার্থক্য, আর practice সহ।
Alternative Classes with Different Interfaces: দুই টিফিন সার্ভিস, দুই ভাষা
একটা টিফিন ডেলিভারির গল্প দিয়ে শেখো এই code smell: দুটো class একই কাজ করে কিন্তু method নাম আলাদা, তাই একটাকে অন্যটার জায়গায় বসানো যায় না। ধাপে ধাপে ঠিক করা দেখো।
Extract Method: একটা বিশাল ফাংশনকে ছোট ছোট নামওয়ালা helper-এ ভাগ করো
Extract Method ধাপে ধাপে শিখে নাও। একটা লম্বা ফাংশন থেকে এলোমেলো block বের করে তাকে একটা পরিষ্কার নাম দাও, আর তোমার কোডকে একটা সহজ to-do লিস্টের মতো পড়ার যোগ্য করে তোলো।