কঠোর অবজেক্ট শেপ ম্যাচিং এর জন্য টাইপস্ক্রিপ্টের এক্স্যাক্ট টাইপস সম্পর্কে জানুন, যা অপ্রত্যাশিত প্রোপার্টি প্রতিরোধ করে এবং কোডের দৃঢ়তা নিশ্চিত করে। এর ব্যবহারিক প্রয়োগ এবং সেরা অনুশীলনগুলি শিখুন।
টাইপস্ক্রিপ্ট এক্স্যাক্ট টাইপস: শক্তিশালী কোডের জন্য কঠোর অবজেক্ট শেপ ম্যাচিং
টাইপস্ক্রিপ্ট, যা জাভাস্ক্রিপ্টের একটি সুপারসেট, ওয়েব ডেভেলপমেন্টের ডাইনামিক জগতে স্ট্যাটিক টাইপিং নিয়ে আসে। যদিও টাইপস্ক্রিপ্ট টাইপ সেফটি এবং কোড রক্ষণাবেক্ষণের ক্ষেত্রে উল্লেখযোগ্য সুবিধা প্রদান করে, তবে এর স্ট্রাকচারাল টাইপিং সিস্টেম কখনও কখনও অপ্রত্যাশিত আচরণের কারণ হতে পারে। এখানেই "এক্স্যাক্ট টাইপস" এর ধারণাটি আসে। যদিও টাইপস্ক্রিপ্টে "এক্স্যাক্ট টাইপস" নামে স্পষ্টভাবে কোনো বিল্ট-ইন বৈশিষ্ট্য নেই, আমরা টাইপস্ক্রিপ্টের বিভিন্ন বৈশিষ্ট্য এবং কৌশলের সমন্বয়ে একই রকম আচরণ অর্জন করতে পারি। এই ব্লগ পোস্টে আমরা টাইপস্ক্রিপ্টে কীভাবে কঠোর অবজেক্ট শেপ ম্যাচিং প্রয়োগ করে কোডের দৃঢ়তা বাড়ানো যায় এবং সাধারণ ভুলগুলো প্রতিরোধ করা যায় তা নিয়ে আলোচনা করব।
টাইপস্ক্রিপ্টের স্ট্রাকচারাল টাইপিং বোঝা
টাইপস্ক্রিপ্ট স্ট্রাকচারাল টাইপিং (ডাক টাইপিং নামেও পরিচিত) ব্যবহার করে, যার মানে হল টাইপের সামঞ্জস্যতা তাদের ঘোষিত নামের পরিবর্তে টাইপের সদস্যদের দ্বারা নির্ধারিত হয়। যদি একটি অবজেক্টের একটি টাইপের জন্য প্রয়োজনীয় সমস্ত প্রোপার্টি থাকে, তবে এটিকে সেই টাইপের সাথে সামঞ্জস্যপূর্ণ বলে মনে করা হয়, এমনকি যদি এর অতিরিক্ত প্রোপার্টিও থাকে।
উদাহরণস্বরূপ:
interface Point {
x: number;
y: number;
}
const myPoint = { x: 10, y: 20, z: 30 };
function printPoint(point: Point) {
console.log(`X: ${point.x}, Y: ${point.y}`);
}
printPoint(myPoint); // এটি ঠিকভাবে কাজ করে, যদিও myPoint-এ 'z' প্রোপার্টি রয়েছে
এই পরিস্থিতিতে, টাইপস্ক্রিপ্ট `myPoint` কে `printPoint` ফাংশনে পাস করার অনুমতি দেয় কারণ এতে প্রয়োজনীয় `x` এবং `y` প্রোপার্টি রয়েছে, যদিও এতে একটি অতিরিক্ত `z` প্রোপার্টি আছে। যদিও এই নমনীয়তা সুবিধাজনক হতে পারে, তবে যদি আপনি অনিচ্ছাকৃতভাবে অপ্রত্যাশিত প্রোপার্টি সহ অবজেক্ট পাস করেন তবে এটি সূক্ষ্ম বাগের কারণ হতে পারে।
অতিরিক্ত প্রোপার্টির সমস্যা
স্ট্রাকচারাল টাইপিংয়ের এই শিথিলতা কখনও কখনও ভুলত্রুটি আড়াল করতে পারে। একটি ফাংশন বিবেচনা করুন যা একটি কনফিগারেশন অবজেক্ট আশা করে:
interface Config {
apiUrl: string;
timeout: number;
}
function setup(config: Config) {
console.log(`API URL: ${config.apiUrl}`);
console.log(`Timeout: ${config.timeout}`);
}
const myConfig = { apiUrl: "https://api.example.com", timeout: 5000, typo: true };
setup(myConfig); // টাইপস্ক্রিপ্ট এখানে কোনো অভিযোগ করে না!
console.log(myConfig.typo); //prints true. অতিরিক্ত প্রোপার্টিটি নীরবে বিদ্যমান
এই উদাহরণে, `myConfig` এ `typo` নামে একটি অতিরিক্ত প্রোপার্টি আছে। টাইপস্ক্রিপ্ট কোনো এরর দেখায় না কারণ `myConfig` এখনও `Config` ইন্টারফেসটি পূরণ করে। যাইহোক, টাইপোটি কখনও ধরা পড়ে না, এবং যদি টাইপোটি `typoo` হওয়ার কথা ছিল, তবে অ্যাপ্লিকেশনটি প্রত্যাশা অনুযায়ী আচরণ নাও করতে পারে। জটিল অ্যাপ্লিকেশন ডিবাগ করার সময় এই আপাতদৃষ্টিতে নগণ্য সমস্যাগুলি বড় মাথাব্যথার কারণ হতে পারে। অবজেক্টের মধ্যে নেস্টেড অবজেক্টের সাথে কাজ করার সময় একটি অনুপস্থিত বা ভুল বানানযুক্ত প্রোপার্টি সনাক্ত করা বিশেষভাবে কঠিন হতে পারে।
টাইপস্ক্রিপ্টে এক্স্যাক্ট টাইপস প্রয়োগের পদ্ধতি
যদিও টাইপস্ক্রিপ্টে সরাসরি "এক্স্যাক্ট টাইপস" উপলব্ধ নয়, তবে একই ফলাফল অর্জন করতে এবং কঠোর অবজেক্ট শেপ ম্যাচিং প্রয়োগ করার জন্য এখানে বেশ কয়েকটি কৌশল রয়েছে:
১. `Omit` সহ টাইপ অ্যাসারশন ব্যবহার করে
`Omit` ইউটিলিটি টাইপ আপনাকে একটি বিদ্যমান টাইপ থেকে নির্দিষ্ট কিছু প্রোপার্টি বাদ দিয়ে একটি নতুন টাইপ তৈরি করতে দেয়। টাইপ অ্যাসারশনের সাথে মিলিত হয়ে, এটি অতিরিক্ত প্রোপার্টি প্রতিরোধ করতে সাহায্য করতে পারে।
interface Point {
x: number;
y: number;
}
const myPoint = { x: 10, y: 20, z: 30 };
// এমন একটি টাইপ তৈরি করুন যাতে শুধুমাত্র Point-এর প্রোপার্টিগুলো অন্তর্ভুক্ত থাকে
const exactPoint: Point = myPoint as Omit & Point;
// Error: Type '{ x: number; y: number; z: number; }' is not assignable to type 'Point'.
// Object literal may only specify known properties, and 'z' does not exist in type 'Point'.
function printPoint(point: Point) {
console.log(`X: ${point.x}, Y: ${point.y}`);
}
//Fix
const myPointCorrect = { x: 10, y: 20 };
const exactPointCorrect: Point = myPointCorrect as Omit & Point;
printPoint(exactPointCorrect);
এই পদ্ধতিটি একটি এরর দেখায় যদি `myPoint`-এ এমন প্রোপার্টি থাকে যা `Point` ইন্টারফেসে সংজ্ঞায়িত নয়।
ব্যাখ্যা: `Omit
২. অবজেক্ট তৈরি করার জন্য একটি ফাংশন ব্যবহার করে
আপনি একটি ফ্যাক্টরি ফাংশন তৈরি করতে পারেন যা শুধুমাত্র ইন্টারফেসে সংজ্ঞায়িত প্রোপার্টিগুলো গ্রহণ করে। এই পদ্ধতিটি অবজেক্ট তৈরির সময় শক্তিশালী টাইপ চেকিং প্রদান করে।
interface Config {
apiUrl: string;
timeout: number;
}
function createConfig(config: Config): Config {
return {
apiUrl: config.apiUrl,
timeout: config.timeout,
};
}
const myConfig = createConfig({ apiUrl: "https://api.example.com", timeout: 5000 });
//This will not compile:
//const myConfigError = createConfig({ apiUrl: "https://api.example.com", timeout: 5000, typo: true });
//Argument of type '{ apiUrl: string; timeout: number; typo: true; }' is not assignable to parameter of type 'Config'.
// Object literal may only specify known properties, and 'typo' does not exist in type 'Config'.
শুধুমাত্র `Config` ইন্টারফেসে সংজ্ঞায়িত প্রোপার্টি দিয়ে একটি অবজেক্ট তৈরি করে রিটার্ন করার মাধ্যমে, আপনি নিশ্চিত করেন যে কোনো অতিরিক্ত প্রোপার্টি প্রবেশ করতে পারবে না। এটি কনফিগারেশন তৈরি করা আরও নিরাপদ করে তোলে।
৩. টাইপ গার্ড ব্যবহার করে
টাইপ গার্ড হলো এমন ফাংশন যা একটি নির্দিষ্ট স্কোপের মধ্যে একটি ভ্যারিয়েবলের টাইপকে সংকুচিত করে। যদিও তারা সরাসরি অতিরিক্ত প্রোপার্টি প্রতিরোধ করে না, তবে তারা আপনাকে স্পষ্টভাবে সেগুলো পরীক্ষা করতে এবং উপযুক্ত ব্যবস্থা নিতে সাহায্য করতে পারে।
interface User {
id: number;
name: string;
}
function isUser(obj: any): obj is User {
return (
typeof obj === 'object' &&
obj !== null &&
'id' in obj && typeof obj.id === 'number' &&
'name' in obj && typeof obj.name === 'string' &&
Object.keys(obj).length === 2 //check for number of keys. Note: brittle and depends on User's exact key count.
);
}
const potentialUser1 = { id: 123, name: "Alice" };
const potentialUser2 = { id: 456, name: "Bob", extra: true };
if (isUser(potentialUser1)) {
console.log("Valid User:", potentialUser1.name);
} else {
console.log("Invalid User");
}
if (isUser(potentialUser2)) {
console.log("Valid User:", potentialUser2.name); //Will not hit here
} else {
console.log("Invalid User");
}
এই উদাহরণে, `isUser` টাইপ গার্ড শুধুমাত্র প্রয়োজনীয় প্রোপার্টির উপস্থিতিই পরীক্ষা করে না, বরং তাদের টাইপ এবং প্রোপার্টির *সঠিক* সংখ্যাও পরীক্ষা করে। এই পদ্ধতিটি আরও স্পষ্ট এবং আপনাকে অবৈধ অবজেক্ট সুন্দরভাবে পরিচালনা করার সুযোগ দেয়। যাইহোক, প্রোপার্টির সংখ্যা পরীক্ষা করাটা ভঙ্গুর। যখনই `User` টাইপে প্রোপার্টি যোগ বা বিয়োগ করা হয়, তখন এই চেকটি আপডেট করতে হবে।
৪. `Readonly` এবং `as const` এর ব্যবহার
যদিও `Readonly` বিদ্যমান প্রোপার্টির পরিবর্তন প্রতিরোধ করে এবং `as const` একটি রিড-অনলি টাপল বা অবজেক্ট তৈরি করে যেখানে সমস্ত প্রোপার্টি ডিপলি রিড-অনলি এবং লিটারেল টাইপ থাকে, তবে অন্যান্য পদ্ধতির সাথে মিলিত হলে এগুলি আরও কঠোর সংজ্ঞা এবং টাইপ চেকিং তৈরি করতে ব্যবহার করা যেতে পারে। যদিও, কোনোটিই নিজে থেকে অতিরিক্ত প্রোপার্টি প্রতিরোধ করে না।
interface Options {
width: number;
height: number;
}
//Create the Readonly type
type ReadonlyOptions = Readonly;
const options: ReadonlyOptions = { width: 100, height: 200 };
//options.width = 300; //error: Cannot assign to 'width' because it is a read-only property.
//Using as const
const config = { api_url: "https://example.com", timeout: 3000 } as const;
//config.timeout = 5000; //error: Cannot assign to 'timeout' because it is a read-only property.
//However, excess properties are still allowed:
const invalidOptions: ReadonlyOptions = { width: 100, height: 200, depth: 300 }; //no error. Still allows excess properties.
interface StrictOptions {
readonly width: number;
readonly height: number;
}
//This will now error:
//const invalidStrictOptions: StrictOptions = { width: 100, height: 200, depth: 300 };
//Type '{ width: number; height: number; depth: number; }' is not assignable to type 'StrictOptions'.
// Object literal may only specify known properties, and 'depth' does not exist in type 'StrictOptions'.
এটি অপরিবর্তনীয়তা (immutability) উন্নত করে, কিন্তু শুধুমাত্র পরিবর্তন প্রতিরোধ করে, অতিরিক্ত প্রোপার্টির অস্তিত্ব নয়। `Omit` বা ফাংশন পদ্ধতির সাথে মিলিত হলে এটি আরও কার্যকর হয়।
৫. লাইব্রেরি ব্যবহার (যেমন, Zod, io-ts)
Zod এবং io-ts-এর মতো লাইব্রেরিগুলি শক্তিশালী রানটাইম টাইপ ভ্যালিডেশন এবং স্কিমা ডেফিনিশন ক্ষমতা প্রদান করে। এই লাইব্রেরিগুলি আপনাকে এমন স্কিমা সংজ্ঞায়িত করতে দেয় যা আপনার ডেটার প্রত্যাশিত শেপকে সঠিকভাবে বর্ণনা করে, যার মধ্যে অতিরিক্ত প্রোপার্টি প্রতিরোধ করাও অন্তর্ভুক্ত। যদিও তারা একটি রানটাইম ডিপেন্ডেন্সি যোগ করে, তারা একটি খুব শক্তিশালী এবং নমনীয় সমাধান প্রদান করে।
Zod দিয়ে উদাহরণ:
import { z } from 'zod';
const UserSchema = z.object({
id: z.number(),
name: z.string(),
});
type User = z.infer;
const validUser = { id: 1, name: "John" };
const invalidUser = { id: 2, name: "Jane", extra: true };
const parsedValidUser = UserSchema.parse(validUser);
console.log("Parsed Valid User:", parsedValidUser);
try {
const parsedInvalidUser = UserSchema.parse(invalidUser);
console.log("Parsed Invalid User:", parsedInvalidUser); // This won't be reached
} catch (error) {
console.error("Validation Error:", error.errors);
}
Zod-এর `parse` মেথডটি একটি এরর থ্রো করবে যদি ইনপুটটি স্কিমার সাথে সঙ্গতিপূর্ণ না হয়, যা কার্যকরভাবে অতিরিক্ত প্রোপার্টি প্রতিরোধ করে। এটি রানটাইম ভ্যালিডেশন প্রদান করে এবং স্কিমা থেকে টাইপস্ক্রিপ্ট টাইপ তৈরি করে, যা আপনার টাইপ ডেফিনিশন এবং রানটাইম ভ্যালিডেশন লজিকের মধ্যে সামঞ্জস্য নিশ্চিত করে।
এক্স্যাক্ট টাইপস প্রয়োগের জন্য সেরা অনুশীলন
টাইপস্ক্রিপ্টে কঠোর অবজেক্ট শেপ ম্যাচিং প্রয়োগ করার সময় বিবেচনা করার জন্য এখানে কিছু সেরা অনুশীলন রয়েছে:
- সঠিক কৌশলটি বেছে নিন: সেরা পদ্ধতিটি আপনার নির্দিষ্ট চাহিদা এবং প্রকল্পের প্রয়োজনীয়তার উপর নির্ভর করে। সহজ ক্ষেত্রে, `Omit` সহ টাইপ অ্যাসারশন বা ফ্যাক্টরি ফাংশনই যথেষ্ট হতে পারে। আরও জটিল পরিস্থিতিতে বা যখন রানটাইম ভ্যালিডেশন প্রয়োজন হয়, তখন Zod বা io-ts-এর মতো লাইব্রেরি ব্যবহার করার কথা বিবেচনা করুন।
- সামঞ্জস্যপূর্ণ হন: আপনার কোডবেস জুড়ে একটি অভিন্ন স্তরের টাইপ সেফটি বজায় রাখতে আপনার নির্বাচিত পদ্ধতিটি ধারাবাহিকভাবে প্রয়োগ করুন।
- আপনার টাইপগুলি ডকুমেন্ট করুন: আপনার ডেটার প্রত্যাশিত শেপ অন্যান্য ডেভেলপারদের কাছে পৌঁছে দেওয়ার জন্য আপনার ইন্টারফেস এবং টাইপগুলি স্পষ্টভাবে ডকুমেন্ট করুন।
- আপনার কোড পরীক্ষা করুন: আপনার টাইপ সীমাবদ্ধতাগুলি প্রত্যাশা অনুযায়ী কাজ করছে কিনা এবং আপনার কোড অবৈধ ডেটা সুন্দরভাবে পরিচালনা করছে কিনা তা যাচাই করার জন্য ইউনিট টেস্ট লিখুন।
- ট্রেড-অফ বিবেচনা করুন: কঠোর অবজেক্ট শেপ ম্যাচিং প্রয়োগ করা আপনার কোডকে আরও শক্তিশালী করতে পারে, তবে এটি ডেভেলপমেন্টের সময়ও বাড়াতে পারে। সুবিধার বিপরীতে খরচগুলি পরিমাপ করুন এবং আপনার প্রকল্পের জন্য সবচেয়ে উপযুক্ত পদ্ধতিটি বেছে নিন।
- ধাপে ধাপে গ্রহণ করুন: যদি আপনি একটি বড় বিদ্যমান কোডবেসে কাজ করেন, তাহলে এই কৌশলগুলি ধীরে ধীরে গ্রহণ করার কথা বিবেচনা করুন, আপনার অ্যাপ্লিকেশনের সবচেয়ে গুরুত্বপূর্ণ অংশগুলি দিয়ে শুরু করুন।
- অবজেক্ট শেপ সংজ্ঞায়িত করার সময় টাইপ এলিয়াসের চেয়ে ইন্টারফেস পছন্দ করুন: ইন্টারফেসগুলি সাধারণত পছন্দ করা হয় কারণ তারা ডিক্লারেশন মার্জিং সমর্থন করে, যা বিভিন্ন ফাইল জুড়ে টাইপ প্রসারিত করার জন্য দরকারী হতে পারে।
বাস্তব-জগতের উদাহরণ
আসুন কিছু বাস্তব-বিশ্বের পরিস্থিতি দেখি যেখানে এক্স্যাক্ট টাইপস উপকারী হতে পারে:
- API রিকোয়েস্ট পেলোড: একটি API-তে ডেটা পাঠানোর সময়, পেলোডটি প্রত্যাশিত স্কিমার সাথে সঙ্গতিপূর্ণ কিনা তা নিশ্চিত করা অত্যন্ত গুরুত্বপূর্ণ। এক্স্যাক্ট টাইপস প্রয়োগ করা অপ্রত্যাশিত প্রোপার্টি পাঠানোর কারণে সৃষ্ট ত্রুটি প্রতিরোধ করতে পারে। উদাহরণস্বরূপ, অনেক পেমেন্ট প্রসেসিং API অপ্রত্যাশিত ডেটার প্রতি অত্যন্ত সংবেদনশীল।
- কনফিগারেশন ফাইল: কনফিগারেশন ফাইলগুলিতে প্রায়শই প্রচুর সংখ্যক প্রোপার্টি থাকে এবং টাইপো সাধারণ ব্যাপার। এক্স্যাক্ট টাইপস ব্যবহার করে এই টাইপোগুলো প্রথম দিকেই ধরা সম্ভব। যদি আপনি একটি ক্লাউড ডেপ্লয়মেন্টে সার্ভার লোকেশন সেট আপ করছেন, তাহলে লোকেশন সেটিংয়ে একটি টাইপো (যেমন, eu-west-1 বনাম eu-wet-1) যদি আগে থেকে ধরা না পড়ে তবে ডিবাগ করা অত্যন্ত কঠিন হয়ে পড়বে।
- ডেটা ট্রান্সফরমেশন পাইপলাইন: এক ফরম্যাট থেকে অন্য ফরম্যাটে ডেটা রূপান্তর করার সময়, আউটপুট ডেটা প্রত্যাশিত স্কিমার সাথে সঙ্গতিপূর্ণ কিনা তা নিশ্চিত করা গুরুত্বপূর্ণ।
- মেসেজ কিউ: একটি মেসেজ কিউ এর মাধ্যমে মেসেজ পাঠানোর সময়, মেসেজ পেলোডটি বৈধ এবং সঠিক প্রোপার্টি ধারণ করছে কিনা তা নিশ্চিত করা গুরুত্বপূর্ণ।
উদাহরণ: আন্তর্জাতিকীকরণ (i18n) কনফিগারেশন
একটি বহু-ভাষিক অ্যাপ্লিকেশনের জন্য অনুবাদ পরিচালনা করার কথা ভাবুন। আপনার কাছে এইরকম একটি কনফিগারেশন অবজেক্ট থাকতে পারে:
interface Translation {
greeting: string;
farewell: string;
}
interface I18nConfig {
locale: string;
translations: Translation;
}
const englishConfig: I18nConfig = {
locale: "en-US",
translations: {
greeting: "Hello",
farewell: "Goodbye"
}
};
//This will be an issue, as an excess property exists, silently introducing a bug.
const spanishConfig: I18nConfig = {
locale: "es-ES",
translations: {
greeting: "Hola",
farewell: "Adiós",
typo: "unintentional translation"
}
};
//Solution: Using Omit
const spanishConfigCorrect: I18nConfig = {
locale: "es-ES",
translations: {
greeting: "Hola",
farewell: "Adiós"
} as Omit & Translation
};
এক্স্যাক্ট টাইপস ছাড়া, একটি অনুবাদ কীতে টাইপো (যেমন `typo` ফিল্ড যোগ করা) অলক্ষিত থেকে যেতে পারে, যার ফলে ইউজার ইন্টারফেসে অনুবাদ অনুপস্থিত থাকতে পারে। কঠোর অবজেক্ট শেপ ম্যাচিং প্রয়োগ করে, আপনি ডেভেলপমেন্টের সময় এই ত্রুটিগুলি ধরতে পারেন এবং সেগুলিকে প্রোডাকশনে পৌঁছানো থেকে বিরত রাখতে পারেন।
উপসংহার
যদিও টাইপস্ক্রিপ্টে বিল্ট-ইন "এক্স্যাক্ট টাইপস" নেই, আপনি `Omit` সহ টাইপ অ্যাসারশন, ফ্যাক্টরি ফাংশন, টাইপ গার্ড, `Readonly`, `as const`, এবং Zod ও io-ts-এর মতো এক্সটার্নাল লাইব্রেরির মতো টাইপস্ক্রিপ্ট বৈশিষ্ট্য এবং কৌশলগুলির সমন্বয়ে একই ফলাফল অর্জন করতে পারেন। কঠোর অবজেক্ট শেপ ম্যাচিং প্রয়োগ করে, আপনি আপনার কোডের দৃঢ়তা উন্নত করতে পারেন, সাধারণ ত্রুটি প্রতিরোধ করতে পারেন এবং আপনার অ্যাপ্লিকেশনগুলিকে আরও নির্ভরযোগ্য করে তুলতে পারেন। আপনার প্রয়োজনের জন্য সবচেয়ে উপযুক্ত পদ্ধতিটি বেছে নিতে ভুলবেন না এবং আপনার কোডবেস জুড়ে এটি প্রয়োগে সামঞ্জস্যপূর্ণ থাকুন। এই পদ্ধতিগুলি সাবধানে বিবেচনা করে, আপনি আপনার অ্যাপ্লিকেশনের টাইপগুলির উপর বৃহত্তর নিয়ন্ত্রণ নিতে পারেন এবং দীর্ঘমেয়াদী রক্ষণাবেক্ষণযোগ্যতা বাড়াতে পারেন।