Long Parameter List: সম্পর্কিত argument গুছিয়ে object করুন

নয়টি positional argument সহজেই গুলিয়ে যায়। সম্পর্কিত মান object-এ গুছিয়ে ও option keyword-only করে রাখা শিখুন, পরীক্ষিত আগে-পরের code-সহ।

অনেক আলাদা argument দুটি object-এ গোছানো

Long Parameter List হলো এমন function যা চার, পাঁচ, নয়টি argument নেয়। call পড়া কঠিন (create_user("a", "b", 24, "c", ...)), ভুল করা সহজ (দুটি boolean উল্টে গেল), আর বাড়ানো কঠিন। নিচের code ও output প্রকাশের আগে Python 3-এ চালানো।

কীভাবে চিনবেন

  • আঙুলে argument গুনতে হয়।
  • call-এ পরপর কয়েকটি literal থাকে, function না খুলে প্রতিটির অর্থ বোঝা যায় না।
  • একই type-এর দুটি parameter পাশাপাশি (দুটি boolean, দুটি string), তাই উল্টে দিলেও চলে।
  • আরেকটি option যোগ করতে প্রতিটি caller বদলাতে হয়।
  • কয়েকটি parameter সবসময় একসঙ্গে ঘোরে (রাস্তা, শহর, zip, দেশ)।

সমাধান

  1. Introduce a Parameter Object। একসঙ্গে থাকা মান (Address, Contact) এক object-এ গুছান।
  2. Preserve Whole Object। function একটি object-এর তিনটি field নিলে object-ই পাঠান।
  3. option keyword-only করুন ও default দিন। signature-এর * caller-কে newsletter=True লিখতে বাধ্য করে, যা নিজেই বর্ণনা।
from dataclasses import dataclass, field

# ---- BEFORE: nine positional parameters, easy to mix up ----
def create_user_before(name, email, age, street, city, zip_code, country, newsletter, active):
    return {"name": name, "email": email, "age": age, "address": f"{street}, {city} {zip_code}, {country}", "newsletter": newsletter, "active": active}

# ---- AFTER: related values grouped, options keyword-only with defaults ----
@dataclass(frozen=True)
class Address:
    street: str
    city: str
    zip_code: str
    country: str
    def __str__(self): return f"{self.street}, {self.city} {self.zip_code}, {self.country}"

@dataclass(frozen=True)
class Contact:
    name: str
    email: str
    age: int

def create_user(contact: Contact, address: Address, *, newsletter=False, active=True):
    return {"name": contact.name, "email": contact.email, "age": contact.age, "address": str(address), "newsletter": newsletter, "active": active}

নতুন create_user দুটি object ও দুটি নামওয়ালা option নেয়। call বাক্যের মতো পড়া যায়, address এক জায়গায় যাচাই হয়, আর পুরনো caller না ভেঙে default দিয়ে নতুন option যোগ করা যায়।

ফল একই প্রমাণ করুন, আর পুরনো ফাঁদ দেখুন

old = create_user_before("Amina", "a@example.com", 24, "12 Road", "Dhaka", "1207", "BD", True, True)
new = create_user(Contact("Amina", "a@example.com", 24), Address("12 Road", "Dhaka", "1207", "BD"), newsletter=True)
print(new)
assert old == new
# The old call, with the last two flags swapped by mistake, still runs and is silently wrong:
print(create_user_before("Amina", "a@example.com", 24, "12 Road", "Dhaka", "1207", "BD", False, True)["newsletter"])
try: create_user(Contact("A", "a@x.com", 1), Address("s", "c", "z", "k"), True)
except TypeError as e: print("error:", e)
{'name': 'Amina', 'email': 'a@example.com', 'age': 24, 'address': '12 Road, Dhaka 1207, BD', 'newsletter': True, 'active': True}
False
error: create_user() takes 2 positional arguments but 3 were given

নতুন call পুরনোটির সঙ্গে হুবহু একই dictionary দেয়। newsletter ও active ভুল করে উল্টে দেওয়া পুরনো function চলে এবং ভুল উত্তর দেয় (newsletter flag False), কারণ দুটোই boolean। নতুনটি positional True মানে না, কারণ option নাম দিয়ে দিতে হয়, ফলে ভুলটি সঙ্গে সঙ্গে TypeError হয়।

নিরাপদে refactor করবেন যেভাবে

  1. বর্তমান function-এর test লিখুন।
  2. parameter object ও সেগুলো নেওয়া নতুন function বানান, তারপর পুরনোটি নতুনটিকে ডাকুক।
  3. caller একটি একটি করে সরান এবং পুরনো function মুছুন।
  4. frozen=True dataclass ব্যবহার করুন যাতে ব্যবহারের সময় object বদলায় না।
  5. option-এর আগে * দিন যাতে নাম দিতেই হয়।

কখন ছেড়ে দেবেন

  • দুটি-তিনটি পরিষ্কার parameter ঠিক আছে।
  • সংখ্যা কমাতে অসম্পর্কিত মান Params বা Options নামের থলেতে ভরবেন না। গোষ্ঠীর বাস্তব অর্থ থাকতে হবে।
  • অনেক option-ওয়ালা public library function-এ একটি object-এর বদলে keyword-only argument চলে।

সাধারণ ভুল

  • সম্ভাব্য সব field-ওয়ালা "god" parameter object।
  • typed object-এর বদলে dictionary ব্যবহার, ফলে key-র বানান ভুল দেরিতে ধরা পড়ে।
  • call-এর মধ্যে ভাগ হওয়া পরিবর্তনযোগ্য parameter object।
  • positional boolean রাখা, যা call-এ অর্থ লুকায়।

মূল কথা: একসঙ্গে থাকা মান object-এ গুছান, টুকরোর বদলে পুরো object পাঠান, আর option keyword-only করুন। call পড়া সহজ হয় এবং গোলমাল error হয়ে ধরা পড়ে।


Code smell ও refactoring সিরিজ, Bloaters: 4 নম্বর (মোট 5)। আগের লেখা: Primitive Obsession: খোলা string ও float-এর বদলে value object.

আরও পড়ুন

Primitive Obsession: খোলা string ও float-এর বদলে value object

খোলা string ও float ভুল মান যেকোনো জায়গায় যেতে দেয়। টাকা ও email-এর ছোট value object কীভাবে নিয়ম যোগ করে ও এক শ্রেণির bug দূর করে শিখুন।

Code smell•৩ মিনিটে পড়া