من استخدام any عشوائياً إلى تجاهل null وundefined، هذه الأخطاء في TypeScript تدمر نوعية الكود وتجعل فريقك يفقد الثقة في النظام. اكتشف كيف تتجنبها بممارسات هندسية حقيقية مستمدة من مشاريع إنتاجية.
في أحد مشاريع الإنتاج الكبيرة لشركة ناشئة في مجال الفنتك، كان لدينا سيرفر Node.js مكتوب بـ TypeScript يتعامل مع 50 ألف طلب في الدقيقة. بعد شهرين من الإطلاق، بدأ السيرفر في التعطل بشكل عشوائي كل يومين دون أي خطأ ظاهر في السجلات. بعد 48 ساعة من التحقيق، اكتشفنا أن المشكلة كانت في سطر واحد فقط: `const user: User = await getUser(id);` حيث كان `getUser` يرجع `User | undefined` لكننا تجاهلنا الاحتمال الثاني. هذا الخطأ البسيط تسبب في تسرب ذاكرة بمعدل 2 ميجابايت لكل طلب، مما أدى إلى استهلاك كامل الذاكرة بعد 12 ساعة من التشغيل المستمر. الحقيقة المؤلمة هي أن TypeScript كان يصرخ علينا بهذا الخطأ عبر رسالة تحذير واضحة، لكننا تجاهلناها لأنها كانت تظهر في بيئة التطوير فقط دون أن تسبب فشلاً فورياً.
الخطأ هنا ليس في اللغة نفسها، بل في كيفية استخدامها. TypeScript مصمم لحمايتك من نفسك، لكن عندما تتجاهل تحذيراته أو تستخدمها بطريقة خاطئة، تصبح مجرد طبقة إضافية من التعقيد دون فائدة حقيقية. في هذا المقال، سنكشف عن الأخطاء الشائعة التي يقع فيها حتى المطورون ذوو الخبرة، ونشرح بالضبط لماذا تحدث، وكيف تؤثر على أداء التطبيق ونوعيته، وكيفية تجنبها بممارسات هندسية سليمة.
الكلمة المفتاحية `any` في TypeScript هي مثل زر الإطفاء في نظام الحماية من الحرائق. بمجرد الضغط عليه، تتحول جميع التحذيرات إلى صمت، لكن الخطر الحقيقي لا يزال موجوداً. المشكلة ليست في استخدام `any` مرة أو مرتين، بل في اعتباره حلاً دائماً بدلاً من كونه حلاً مؤقتاً. عندما تستخدم `any`، فإنك تخبر TypeScript: "ثق بي، أنا أعرف ما أفعله"، لكن الحقيقة هي أنك غالباً لا تعرف. في مشروع سابق، وجدنا أن 15% من الكود كان يستخدم `any` بشكل صريح أو ضمني (عبر `as any`)، مما أدى إلى 42 خطأ في الإنتاج خلال ستة أشهر كان من الممكن اكتشافها بسهولة في وقت التطوير.
الجانب الأكثر خطورة في `any` هو أنه ينتشر كالسرطان في قاعدة الكود. بمجرد استخدامه في مكان واحد، يبدأ في الظهور في أماكن أخرى لأن TypeScript يفقد قدرته على تتبع الأنواع. على سبيل المثال، إذا كان لديك دالة ترجع `any`، فإن أي دالة تستدعيها ستضطر للتعامل مع النوع كالتالي: `const result = someFunction() as SomeType;` وهذا يؤدي إلى سلسلة من التحويلات القسرية التي تجعل النظام بأكمله غير موثوق به. الحل هنا ليس تجنب `any` تماماً، بل استخدامه فقط في الحالات التي لا يمكن تجنبها (مثل التعامل مع مكتبات JavaScript قديمة) مع وضع تعليق يشرح السبب بوضوح.
// ❌ خطأ شائع: استخدام any لتجنب كتابة الأنواع
function processData(data: any) {
// لا يوجد أي فحص للنوع هنا
return data.map((item: any) => item.value * 2);
}
// ✅ الحل الأفضل: استخدام النوع الصحيح أو unknown مع فحص النوع
function processDataSafe(data: unknown) {
if (!Array.isArray(data)) {
throw new Error("Expected an array");
}
return data.map((item) => {
if (typeof item !== "object" || item === null || !("value" in item)) {
throw new Error("Invalid item structure");
}
return item.value * 2;
});
}
// مثال واقعي: التعامل مع بيانات من API خارجي
interface ApiResponse {
status: string;
data?: Array<{ id: number; name: string }>;
}
function handleApiResponse(response: unknown) {
const parsed = response as ApiResponse; // ❌ خطر: قد يفشل في وقت التشغيل
// ✅ أفضل: فحص النوع قبل الاستخدام
if (typeof response !== "object" || resp== null) {
throw new Error("Invalid response format");
}
const safeResponse = response as { status?: unknown; data?: unknown };
if (safeResponse.status !== "success" || !Array.isArray(safeResponse.data)) {
throw new Error("Unexpected response structure");
}
return safeResponse.data;
}في عام 2021، اكتشف باحثو أمن في شركة GitHub ثغرة أمنية خطيرة في أحد مشاريعهم المفتوحة المصدر. السبب؟ تجاهل احتمال وجود `null` في قيمة مرتجعة من دالة. المهاجم كان قادراً على حقن قيمة `null` في مكان غير متوقع، مما أدى إلى تجاوز صلاحيات والوصول إلى بيانات حساسة. هذا النوع من الأخطاء ليس نادراً، بل هو شائع جداً في قواعد الكود التي تستخدم TypeScript دون الاستفادة الكاملة من نظام الأنواع القوي الخاص به.
المشكلة الأساسية هنا هي أن المطورين يعتادون على كتابة الكود في بيئة مثالية حيث كل شيء يعمل كما هو متوقع. لكن في الواقع، البيانات تأتي من مصادر غير موثوقة: قواعد البيانات، APIs خارجية، مدخلات المستخدم، وحتى مكتبات الطرف الثالث. عندما تكتب `const user: User = getUser(id);`، فإنك تفترض أن `getUser` سترجع دائماً كائن `User`، لكن ماذا لو لم تجد المستخدم؟ ماذا لو كان هناك خطأ في قاعدة البيانات؟ في هذه الحالات، سترجع الدالة `undefined` أو `null`، وسيحاول TypeScript تحويل هذه القيمة إلى `User`، مما يؤدي إلى خطأ في وقت التشغيل.
// ❌ خطأ شائع: تجاهل احتمالية null/undefined
function getUserName(id: string): string {
const user = database.getUser(id); // قد يرجع User | undefined
return user.name; // خطأ في وقت التشغيل إذا كان user هو undefined
}
// ✅ الحل الأفضل: التعامل مع جميع الاحتمالات
function getUserNameSafe(id: string): string {
const user = database.getUser(id);
if (!user) {
throw new Error(`User with id ${id} not found`);
}
return user.name;
}
// استخدام النوع Union بشكل صحيح
function getUserNameUnion(id: string): string | null {
const user = database.getUser(id);
return user?.name ?? null;
}
// مثال متقدم: استخدام النوع Option من fp-ts
import { Option, some, none } from "fp-ts/Option";
function getUserNameOption(id: string): Option<string> {
const user = database.getUser(id);
return user ? some(user.name) : none;
}
// استخدام النوع في مكان آخر
const name = getUserNameOption("123");
name.fold(
() => console.log("User not found"),
(n) => console.log(`User name: ${n}`)
);الفرق بين الكود الآمن وغير الآمن هنا ليس فقط في عدد الأسطر، بل في كيفية تعامل النظام مع الحالات الحدية. عندما تستخدم `string | null` بدلاً من `string` فقط، فإنك تجبر نفسك والمطورين الآخرين على التعامل مع جميع الاحتمالات. هذا ليس مجرد تحسين في النوعية، بل هو حماية حقيقية ضد الأخطاء التي قد لا تظهر إلا في ظروف معينة، مثل عندما يكون السيرفر تحت ضغط كبير أو عندما تكون قاعدة البيانات بطيئة.
في أحد المشاريع الكبيرة التي عملت عليها، كان لدينا ملف واحد يحتوي على أكثر من 500 سطر من الكود يستخدم النوع العام `T` في كل مكان. كان الملف مسؤولاً عن معالجة البيانات من مصادر مختلفة، وكان المطور الأصلي يعتقد أنه يجعل الكود "مرناً وقابلاً لإعادة الاستخدام". لكن الحقيقة هي أن هذا الملف أصبح كابوساً للصيانة. عندما حاولنا إضافة ميزة جديدة، استغرق الأمر ثلاثة أيام فقط لفهم ما يفعله الكود بالفعل. المشكلة لم تكن في استخدام النوع العام نفسه، بل في الإفراط فيه دون داعٍ.
النوع العام `Generics` في TypeScript أداة قوية جداً، لكنها مثل أي أداة قوية، يمكن أن تسبب ضرراً كبيراً إذا استخدمت بشكل غير صحيح. عندما ترى دالة مثل هذه: `function process<T, U, V>(data: T, mapper: (item: T) => U, validator: (result: U) => V): V`، فاعلم أنك أمام مشكلة حقيقية. هذا النوع من الكود يصعب قراءته، وصعب اختباره، وصعب صيانته. السبب هو أن النوع العام يجب أن يستخدم عندما يكون هناك نمط متكرر يمكن تعميمه، وليس لمجرد أنك لا تريد كتابة نوع محدد.
// ❌ خطأ شائع: الإفراط في استخدام النوع العام
function processData<T, U>(data: T[], mapper: (item: T) => U): U[] {
return data.map(mapper);
}
// على الرغم من أن هذا الكود يعمل، إلا أنه غير ضروري هنا
// لأنه يمكن استبداله بنوع محدد
// ✅ الحل الأفضل: استخدام النوع المحدد عندما يكون ممكناً
function processUserData(data: User[], mapper: (user: User) => ProcessedUser): ProcessedUser[] {
return data.map(mapper);
}
// مثال متوازن: استخدام النوع العام عندما يكون مفيداً حقاً
interface ApiResponse<T> {
status: string;
data?: T;
}
async function fetchData<T>(url: string): Promise<T> {
const resp await fetch(url);
const json: ApiResponse<T> = await response.json();
if (json.status !== "success" || !json.data) {
throw new Error("Invalid response");
}
return json.data;
}
// استخدام النوع العام مع القيود
function mergeObjects<T extends object, U extends object>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 };
}
// مثال على كيفية جعل الكود أكثر قابلية للقراءة
// بدلاً من:
function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
// يمكن كتابته بشكل أوضح:
function getObjectValue<T>(obj: T, key: keyof T) {
return obj[key];
}القاعدة الذهبية هنا هي: استخدم النوع العام فقط عندما يكون هناك نمط حقيقي يمكن تعميمه، وعندما يكون استخدامه يجعل الكود أكثر وضوحاً وليس أكثر تعقيداً. إذا وجدت نفسك تستخدم النوع العام في كل مكان، فاسأل نفسك: هل هذا ضروري حقاً؟ هل يجعل الكود أكثر قابلية لإعادة الاستخدام، أم أنه يجعله أكثر صعوبة في الفهم؟ في معظم الحالات، ستجد أن النوع المحدد هو الخيار الأفضل.
في أحد المشاريع التي عملت عليها، كان لدينا قاعدة بيانات تحتوي على أكثر من 300 تحذير من TypeScript في بيئة التطوير. كان الفريق يعتبر هذه التحذيرات "غير مهمة" لأنها لم تسبب أخطاء في وقت التشغيل. لكن بعد ستة أشهر، عندما حاولنا ترقية إصدار TypeScript، وجدنا أن 40% من هذه التحذيرات أصبحت أخطاء فعلية تمنع بناء المشروع. استغرق الأمر أسبوعين كاملين لإصلاح هذه الأخطاء، وكان معظمها يتعلق بتغييرات في كيفية تعامل TypeScript مع الأنواع الضمنية.
المشكلة هنا ليست في التحذيرات نفسها، بل في تجاهلها. كل تحذير من TypeScript هو إشارة إلى مشكلة محتملة في الكود. قد لا تسبب هذه المشكلة خطأ فورياً، لكنها قد تؤدي إلى سلوك غير متوقع في ظروف معينة. على سبيل المثال، تحذير مثل "Object is possibly 'undefined'" قد لا يسبب مشكلة في معظم الحالات، لكنه قد يؤدي إلى خطأ في وقت التشغيل عندما يكون السيرفر تحت ضغط كبير أو عندما تكون البيانات غير مكتملة.
// مثال على تحذير شائع يتم تجاهله
interface User {
id: string;
name: string;
email?: string; // هذا الحقل اختياري
}
function sendEmail(user: User) {
// ❌ خطأ: قد يكون email هو undefined
console.log(`Sending email to ${user.email.toLowerCase()}`);
// TypeScript سيظهر تحذيراً هنا
}
// ✅ الحل الصحيح: التعامل مع جميع الاحتمالات
function sendEmailSafe(user: User) {
if (!user.email) {
throw new Error("User email is required");
}
console.log(`Sending email to ${user.email.toLowerCase()}`);
}
// مثال آخر: تجاهل التحذيرات في المكتبات الخارجية
// ❌ خطأ: تجاهل التحذيرات من المكتبة
// @ts-ignore
const result = someExternalLibrary.process(data);
// ✅ الحل الأفضل: كتابة نوع صحيح للمكتبة
interface ExternalLibrary {
process: (data: unknown) => ProcessedData;
}
declare const someExternalLibrary: ExternalLibrary;
const safeResult = someExternalLibrary.process(data);الحل هنا ليس فقط إصلاح التحذيرات عندما تظهر، بل أيضاً إعداد بيئة التطوير بحيث لا تسمح بتجاهلها. يمكنك تكوين TypeScript لجعل بعض التحذيرات أخطاء فعلية عبر ملف `tsconfig.json`. على سبيل المثال، يمكنك تعيين `strictNullChecks` إلى `true` لجعل جميع التحذيرات المتعلقة بـ `null` و `undefined` أخطاء فعلية. هذا قد يبدو صارماً في البداية، لكنه سيوفر عليك ساعات من البحث عن الأخطاء في وقت لاحق.
في أحد المشاريع المالية التي عملت عليها، كان لدينا دالة تقوم بمعالجة المعاملات المالية. كان النوع المستخدم لهذه الدالة هو `string | number`، مما يعني أن الدالة يمكنها قبول إما سلسلة نصية أو رقم. هذا يبدو مرناً في البداية، لكنه أدى إلى مشاكل حقيقية عندما بدأنا في معالجة المعاملات الكبيرة. بعض المعاملات كانت تأتي كسلاسل نصية (مثل "1000.50")، بينما كانت أخرى تأتي كأرقام (مثل 1000.5). عندما حاولنا إجراء عمليات حسابية على هذه القيم، وجدنا أن بعض العمليات كانت تعطي نتائج غير صحيحة بسبب تحويل الأنواع الضمني.
المشكلة هنا ليست في استخدام النوع Union نفسه، بل في استخدامه دون فهم كامل لكيفية تعامل TypeScript معه. عندما تستخدم `string | number`، فإنك تخبر TypeScript أن القيمة يمكن أن تكون أياً من النوعين، لكنك لا توفر أي معلومات عن متى تستخدم أي نوع. هذا يؤدي إلى كود غير آمن وغير متوقع. على سبيل المثال، إذا كتبت `const amount: string | number = "1000";` ثم حاولت إجراء عملية حسابية مثل `amount * 1.1`، فإن TypeScript سيحول السلسلة النصية إلى رقم تلقائياً، لكن هذا قد لا يكون السلوك المتوقع دائماً.
// ❌ خطأ شائع: استخدام النوع Union دون داعٍ
function processAmount(amount: string | number) {
return amount * 1.1; // قد يعمل، لكن غير آمن
}
// ✅ الحل الأفضل: استخدام نوع محدد أو تحويل النوع بشكل صريح
function processAmountSafe(amount: string | number) {
const numericAmount = typeof amount === "string" ? parseFloat(amount) : amount;
if (isNaN(numericAmount)) {
throw new Error("Invalid amount");
}
return numericAmount * 1.1;
}
// مثال أفضل: استخدام نوع محدد لكل حالة
interface StringAmount {
type: "string";
value: string;
}
interface NumberAmount {
type: "number";
value: number;
}
type Amount = StringAmount | NumberAmount;
function processAmountTyped(amount: Amount) {
const numericAmount = amount.type === "string" ? parseFloat(amount.value) : amount.value;
return numericAmount * 1.1;
}
// مثال متقدم: استخدام النوع branded لتجنب الخلط
declare const StringAmountBrand: unique symbol;
declare const NumberAmountBrand: unique symbol;
type BrandedStringAmount = string & { [StringAmountBrand]: never };
type BrandedNumberAmount = number & { [NumberAmountBrand]: never };
function createStringAmount(value: string): BrandedStringAmount {
return value as BrandedStringAmount;
}
function createNumberAmount(value: number): BrandedNumberAmount {
return value as BrandedNumberAmount;
}
function processBrandedAmount(amount: BrandedStringAmount | BrandedNumberAmount) {
const numericAmount = typeof amount === "string" ? parseFloat(amount) : amount;
return numericAmount * 1.1;
}الحل الأمثل هنا هو تجنب النوع Union عندما يكون هناك بديل أكثر دقة. إذا كانت القيم تأتي من مصادر مختلفة، ففكر في استخدام أنواع محددة لكل مصدر بدلاً من محاولة دمجها في نوع واحد. على سبيل المثال، بدلاً من استخدام `string | number`، يمكنك استخدام نوعين مختلفين مثل `StringAmount` و `NumberAmount` مع حقل `type` يميز بينهما. هذا يجعل الكود أكثر أماناً وأسهل في الصيانة.
في أحد المشاريع الكبيرة، كان لدينا واجهة `User` تحتوي على أكثر من 30 حقلاً. عندما أردنا إنشاء مستخدم جديد، كنا نحتاج إلى تمرير جميع هذه الحقول إلى الدالة، حتى لو كانت معظمها اختيارية. هذا أدى إلى كود غير ضروري ومعقد، حيث كنا نضطر إلى تمرير قيم افتراضية لكل حقل اختياري. الحل الذي استخدمناه كان النوع الجزئي `Partial<User>`، الذي يجعل جميع حقول الواجهة اختيارية. لكن المشكلة ظهرت عندما بدأنا في استخدام هذا النوع في كل مكان دون تفكير.
النوع الجزئي `Partial<T>` أداة قوية جداً، لكنها يمكن أن تصبح خطيرة إذا استخدمت بشكل غير صحيح. عندما تجعل جميع حقول الواجهة اختيارية، فإنك تخسر الكثير من فوائد TypeScript. على سبيل المثال، إذا كان لديك واجهة `User` تحتوي على حقل `email` مطلوب، واستخدمت `Partial<User>` في مكان ما، فإنك تسمح بإنشاء مستخدم بدون بريد إلكتروني، مما قد يؤدي إلى أخطاء في وقت التشغيل عندما يحاول النظام إرسال بريد إلكتروني لهذا المستخدم.
// ❌ خطأ شائع: استخدام Partial بشكل غير مدروس
interface User {
id: string;
name: string;
email: string;
age?: number;
}
function createUser(user: Partial<User>): User {
return {
id: user.id || generateId(),
name: user.name || "Unknown",
email: user.email || "", // ❌ خطير: يسمح بإنشاء مستخدم بدون بريد إلكتروني
age: user.age,
};
}
// ✅ الحل الأفضل: استخدام النوع الصحيح لكل حالة
interface UserRequired {
name: string;
email: string;
}
interface UserOptional {
id?: string;
age?: number;
}
type User = UserRequired & UserOptional;
function createUserSafe(user: UserRequired & Partial<UserOptional>): User {
return {
id: user.id || generateId(),
name: user.name,
email: user.email, // ✅ مطلوب
age: user.age,
};
}
// مثال متقدم: استخدام النوع Pick و Omit
function updateUser(userId: string, updates: Partial<Omit<User, "id">>): User {
const existingUser = database.getUser(userId);
if (!existingUser) {
throw new Error("User not found");
}
return { ...existingUser, ...updates };
}الحل هنا هو استخدام النوع الجزئي فقط عندما يكون ذلك ضرورياً بالفعل. بدلاً من جعل جميع الحقول اختيارية، فكر في تقسيم الواجهة إلى أجزاء مطلوبة واختيارية. على سبيل المثال، يمكنك إنشاء واجهة `UserRequired` تحتوي على الحقول المطلوبة فقط، وواجهة `UserOptional` تحتوي على الحقول الاختيارية، ثم دمجهما معاً. هذا يجعل الكود أكثر أماناً وأسهل في الفهم.
في أحد المشاريع التي عملت عليها، كان لدينا ملف يحتوي على أكثر من 200 سطر من الكود مخصص لتعريف أنواع جديدة بناءً على أنواع موجودة. على سبيل المثال، كان لدينا نوع `UserWithoutId` الذي كان نسخة من الواجهة `User` بدون حقل `id`. كان هذا النوع يستخدم في عدة أماكن، وكان علينا تحديثه يدوياً في كل مرة نغير فيها واجهة `User`. هذا النوع من الكود ليس فقط غير ضروري، بل هو أيضاً مصدر للأخطاء، لأنه يمكن أن ينسى المطور تحديث النوع المشتق عندما يغير النوع الأصلي.
TypeScript يوفر مجموعة قوية من الأنواع المشتقة `Utility Types` التي تسمح لك بإنشاء أنواع جديدة بناءً على أنواع موجودة دون الحاجة إلى إعادة تعريفها يدوياً. على سبيل المثال، بدلاً من تعريف `UserWithoutId` يدوياً، يمكنك استخدام `Omit<User, "id">`. هذا النوع سيقوم تلقائياً بإنشاء نوع جديد بدون حقل `id`، وسيبقى محدثاً تلقائياً إذا تغيرت واجهة `User`. هذا ليس فقط يوفر الوقت، بل يقلل أيضاً من فرص حدوث أخطاء بسبب عدم تحديث الأنواع المشتقة.
// ❌ خطأ شائع: إعادة تعريف الأنواع يدوياً
interface User {
id: string;
name: string;
email: string;
age?: number;
}
interface UserWithoutId {
name: string;
email: string;
age?: number;
}
// ✅ الحل الأفضل: استخدام الأنواع المشتقة
function createUser(user: Omit<User, "id">): User {
return { ...user, id: generateId() };
}
// مثال آخر: استخدام Pick
function getUserNameAndEmail(user: Pick<User, "name" | "email">) {
return `${user.name} <${user.email}>`;
}
// مثال متقدم: استخدام Record
function createUserLookup(users: User[]): Record<string, Omit<User, "id">> {
return users.reduce((acc, user) => {
const { id, ...rest } = user;
acc[id] = rest;
return acc;
}, {} as Record<string, Omit<User, "id">>);
}
// مثال على استخدام Partial مع Readonly
function updateUser(userId: string, updates: Partial<Readonly<User>>): User {
const existingUser = database.getUser(userId);
if (!existingUser) {
throw new Error("User not found");
}
return { ...existingUser, ...updates };
}الأنواع المشتقة في TypeScript ليست مجرد اختصار، بل هي أداة قوية لتحسين نوعية الكود وتقليل الأخطاء. عندما تستخدم `Omit` أو `Pick` أو `Partial`، فإنك تجعل الكود أكثر قابلية للصيانة وأكثر أماناً. إذا وجدت نفسك تعيد تعريف أنواع موجودة، فاسأل نفسك: هل هناك نوع مشتق يمكنني استخدامه بدلاً من ذلك؟ في معظم الحالات، ستكون الإجابة نعم.
TypeScript أداة قوية، لكنها ليست سحرية. يمكنها اكتشاف الأخطاء في وقت التطوير فقط إذا استخدمتها بالطريقة الصحيحة. القاعدة الأولى هي: لا تتجاهل تحذيرات TypeScript أبداً، حتى لو كانت تبدو غير مهمة. كل تحذير هو إشارة إلى مشكلة محتملة، وإذا تجاهلتها، فقد تظهر في أسوأ وقت ممكن. القاعدة الثانية هي: استخدم الأنواع الدقيقة قدر الإمكان. كلما كانت الأنواع أكثر تحديداً، كلما كان الكود أكثر أماناً وأسهل في الصيانة. القاعدة الثالثة هي: لا تستخدم `any` إلا في الحالات التي لا يمكن تجنبها، ومع تعليق يشرح السبب بوضوح.
النصيحة الأخيرة والأهم هي: تعامل مع TypeScript كجزء لا يتجزأ من عملية التطوير، وليس كطبقة إضافية تضاف في النهاية. عندما تكتب كوداً جديداً، فكر أولاً في الأنواع، ثم في التنفيذ. استخدم الأنواع المشتقة والأنواع الجزئية لجعل الكود أكثر مرونة دون التضحية بالأمان. وعندما تواجه تحذيراً، لا تتجاهله أو تستخدم `any` لتجاوزه، بل حل المشكلة بشكل صحيح. بهذه الطريقة، ستجعل TypeScript يعمل لصالحك، وليس ضدك.
TypeScript ليس مجرد أداة لتحسين الكود، بل هو طريقة تفكير مختلفة في البرمجة. عندما تبدأ في التفكير بالنوع أولاً، ستجد أن الكود الذي تكتبه أكثر أماناً وأسهل في الفهم والصيانة.
— مهندس برمجيات سنيور في Nouvil