بازگشت به بلاگ
۲۵ فروردین ۱۴۰۵Sergei Solod5 دقیقه مطالعه

چرا TypeScript در توسعهٔ نرم‌افزار واقعی با Codex این‌قدر خوب کار می‌کند

در محیط production، TypeScript چیزی به Codex می‌دهد که حتی یک prompt خوب هم جای آن را نمی‌گیرد: قراردادهای قابل‌بررسی توسط ماشین، بازخورد سریع compiler و مسیر امن‌تر برای refactorهای بزرگ.

TypeScriptCodexکدنویسی با هوش مصنوعیتحویل نرم‌افزارJavaScriptگردش کار توسعه

قبلاً این نظر را عمداً تندتر بیان می‌کردم: TypeScript بهترین زبان برای Codex است. امروز دقیق‌تر می‌گویم. برای نوع توسعهٔ frontend و full-stack در production که خودم انجام می‌دهم، TypeScript محیطی است که در آن کنترل، بررسی و اعتماد منطقی به Codex آسان‌تر است.

کلمهٔ کلیدی اینجا «TypeScript» نیست؛ کنترل‌پذیری است. AI coding agentها در ساختن کدی که منطقی به نظر می‌رسد عالی‌اند. اما مهندسی production سخت‌گیرانه‌تر است: کد باید قراردادهای موجود را رعایت کند، رفتار را حفظ کند، refactor را تحمل کند و وقتی فرضی اشتباه است سریع شکست بخورد. TypeScript نسبت به JavaScript ساده تعداد بیشتری از این محدودیت‌ها را به شکل قابل‌خواندن و قابل‌بررسی برای ماشین در می‌آورد.

Codex واقعاً از یک codebase چه می‌خواهد؟

prompt خوب کمک می‌کند، اما فقط یکی از منابع context است. خود codebase می‌تواند کار را توضیح دهد یا مدل را مجبور به حدس زدن کند. در عمل، بهترین محیط جایی است که فرض‌های مهم در کد دیده شوند و خودکار بررسی شوند.

  • امضای توابع ورودی و خروجی مجاز را مشخص می‌کند.
  • interface و type ساختار domain objectها را صریح می‌کند.
  • union و enum حالت‌های معتبر را محدود می‌کند.
  • compiler بسیاری از فرض‌های غلط را بلافاصله بعد از edit به error مشخص تبدیل می‌کند.

اگر codebase هنگام شکستن قرارداد فوراً واکنش نشان دهد، AI agent لازم نیست کل برنامه را بی‌نقص بفهمد. هرچه feedback loop کوتاه‌تر باشد، جا برای حدس مطمئن کمتر می‌شود.

Typeها context قابل‌اجرا هستند، نه فقط مستندات

TypeScript خوب نیت طراحی را برای انسان توضیح می‌دهد، اما در workflow خودکار ارزش بیشتری دارد: آن مستندات قابل‌بررسی‌اند. مثلاً:

type PaymentResult =
  | { ok: true; receiptId: string }
  | { ok: false; code: "declined" | "timeout" };

function getReceiptId(result: PaymentResult) {
  if (result.ok) return result.receiptId;

  return result.receiptId;
  // TypeScript: Property 'receiptId' does not exist
  // on the failure branch.
}

در JavaScript همین خطا ممکن است تا زمان اجرای آن branch در runtime طبیعی به نظر برسد. TypeScript می‌تواند فرض غلط را پیش از merge رد کند. برای Codex، compiler error فقط خط قرمز editor نیست؛ علامت دقیقی است از چیزی که مدل اشتباه فهمیده.

برای همین domain typeهای دقیق را به ساختارهای خیلی باز مثل Record<string, any> ترجیح می‌دهم. هرچه typeها واقعیت را بهتر توصیف کنند، codebase context بهتری برای توسعه‌دهنده و AI می‌شود.

Compiler اشتباه را به feedback loop تبدیل می‌کند

به روش «از Codex کد بخواه و جواب اول را بپذیر» اعتماد ندارم. روند قابل‌اعتماد تکراری است:

task → inspect → edit → typecheck → lint → test → review diff

این روند هیجان‌انگیز نیست و دقیقاً به همین دلیل کار می‌کند. بعد از هر تغییر معنی‌دار، محیط پاسخ می‌دهد. Type error قرارداد شکسته را پیدا می‌کند، lint مشکلات دیگری را می‌گیرد، test رفتاری را بررسی می‌کند که type نمی‌تواند بیان کند و diff نهایی همچنان review انسانی می‌خواهد.

prompt هوشمند شاید تلاش اول را بهتر کند؛ verification loop قوی همهٔ تلاش‌ها را بهتر می‌کند. برای production این مهم‌تر است.

در refactorهای بزرگ ارزش TypeScript بیشتر دیده می‌شود

تغییر کوچک و مستقل برای تقریباً هر coding assistant ساده است. سختی زمانی است که تغییر به ده‌ها فایل می‌رسد: تغییر نام field دامنه، تغییر API response، محدودتر کردن component props، شکستن union یا جایگزینی abstraction قدیمی.

در TypeScript، breaking change نقشه‌ای از فرض‌های تحت‌تأثیر می‌سازد. compiler نشان می‌دهد قرارداد قدیمی کجا هنوز استفاده می‌شود. Codex می‌تواند این فهرست را یکی‌یکی اصلاح و بعد دوباره بررسی کند. این refactor را خودکار درست نمی‌کند، اما blast radius را قابل‌دیدن می‌کند.

TypeScript چه چیزهایی را حل نمی‌کند؟

TypeScript guardrail است، نه اثبات درستی.

  • Business logic غلط: کد کاملاً typed می‌تواند نتیجهٔ اشتباه بدهد.
  • Runtime input: API، دیتابیس، form یا سرویس خارجی ممکن است داده‌ای خلاف فرض compile-time برگرداند.
  • Context محصول: compiler نمی‌داند یک flow چرا وجود دارد یا کدام edge case برای کاربر مهم است.
  • Security و architecture: type معتبر authorization، caching، مرزهای دیتابیس یا infrastructure درست را تضمین نمی‌کند.

بنابراین runtime validation، test، logging، observability و review ضروری می‌مانند. typecheck تمیز به معنی feature درست نیست.

Workflow من برای تغییرات AI-assisted

  1. هدف را محدود می‌کنم. چه چیزی باید عوض شود، چه چیزی نباید عوض شود و acceptance criteria چیست.
  2. قبل از edit از Codex می‌خواهم بررسی کند. typeهای موجود، call siteها، testها و moduleهای اطراف اغلب از prompt طولانی مفیدترند.
  3. کوچک‌ترین تغییر منسجم را ترجیح می‌دهم. diff متمرکز آسان‌تر از rewrite نامرتبط بررسی می‌شود.
  4. فوراً typecheck و lint اجرا می‌کنم. خطای ساختاری باید وقتی تغییر کوچک است ظاهر شود.
  5. test مرتبط را اجرا و با تغییر رفتار test اضافه می‌کنم. type قرارداد را و test رفتار را بررسی می‌کند.
  6. diff نهایی را خودم review می‌کنم. naming، architecture، duplication، edge case و حل واقعی مسئله.

چه زمانی JavaScript کافی است؟

این به معنی بد بودن JavaScript نیست. برای script کوچک، prototype موقت، automation ساده یا کدی با shared state کم، ساختار اضافی TypeScript شاید ارزش زیادی نداشته باشد. پروژهٔ JavaScript با test قوی و convention روشن هم می‌تواند محیط خوبی برای AI باشد.

ادعای من محدودتر است: هرچه codebase بزرگ‌تر، ماندگارتر و گروهی‌تر شود، محدودیت‌های قابل‌بررسی توسط ماشین ارزش بیشتری پیدا می‌کنند. همان‌جا هزینهٔ اشتباه AI هم بیشتر می‌شود.

دلیل واقعی ترجیح TypeScript با Codex

TypeScript خود Codex را باهوش‌تر نمی‌کند؛ محیط را نسبت به حدس بد کم‌تحمل‌تر می‌کند.

در production فقط agent خلاق نمی‌خواهم؛ agentی می‌خواهم که داخل سیستمی کار کند که مرتب به او بگوید چه زمانی اشتباه کرده. type، compiler error، lint، test و review انسانی با هم چنین سیستمی می‌سازند.

پس وقتی می‌گویم TypeScript با Codex عالی کار می‌کند، مسئله تعصب زبانی نیست. مسئلهٔ feedback سریع و قابل‌بررسی توسط ماشین است: ابهام کمتر، blast radius کوچک‌تر، refactor امن‌تر و سرعت مهندسی قابل‌اعتمادتر وقتی verification جدی گرفته شود.