ہر PDF لوڈ پر ایک ہی ReferenceError کے ساتھ کریش ہو رہا تھا۔ اسٹیک ٹریس (stack trace) کسی مفید جگہ کی نشاندہی نہیں کر رہا تھا، اور وہ اسپیسیفیکیشن (specification) جس کی بنیاد پر کوڈ لکھا گیا تھا، کاغذ پر بالکل معقول لگ رہی تھی۔ اس میں کہا گیا تھا کہ ایک فیچر فلیگ (feature flag) کو چیک کریں، اور ہر ریجن (region) کا موازنہ pageWidth کے نصف سے کریں۔ اس نے یہ تو بتایا کہ کیا ہونا چاہیے، لیکن یہ بتانے میں ناکام رہا کہ pageWidth کہاں سے آنا چاہیے، اور اس ایک کمی نے پورے پائپ لائن کو تباہ کرنے کے لیے کافی تھی۔
آرکیٹیکچر دستاویزات رویے (behavior) کی وضاحت کرنے میں تو اچھی ہوتی ہیں، لیکن وہ حدود (boundaries) کی وضاحت کرنے میں اکثر بہت بری ہوتی ہیں۔ ایک جملہ جو کہتا ہے کہ "فنکشن pageWidth کے خلاف x کو چیک کرتا ہے" وہ کوئی تکنیکی معاہدہ (technical contract) نہیں ہے؛ بلکہ یہ ایک بیانیہ ہے جو سادہ انگریزی کے پیچھے ایک انحصار (dependency) کو چھپا دیتا ہے۔ جب ایک ڈویلپر وہ جملہ پڑھتا ہے اور ایک ماڈیول لیول فنکشن لکھتا ہے جو نام کے ذریعے pageWidth کا حوالہ دیتا ہے، تو کوڈ درست نظر آتا ہے کیونکہ وہ تفصیلات پر پورا اترتا ہے۔ پھر رن ٹائم (runtime) اس نام کو حل کرنے کی کوشش کرتا ہے، اسکوپ میں کچھ نہیں پاتا، اور ایرر (error) دے دیتا ہے۔
وہ ریفیکٹرنگ جو سادہ ہونی چاہیے تھی
میں نے پیج اسمبلی ریفیکٹرنگ کے دوران بالکل یہی پیٹرن دیکھا۔ اسپیسیفیکیشن میں دو بظاہر صاف تقاضے درج تھے:
FEATURE_LAYOUTکو چیک کریں۔- ہر ریجن کا موازنہ
pageWidth / 2سے کریں۔
ڈویلپر نے ہدایات پر مکمل وفاداری سے عمل کیا۔ انہوں نے ماڈیول اسکوپ پر ایک یوٹیلیٹی فنکشن نکالا اور pageWidth کو اسے پیرامیٹر کے طور پر متعارف کرائے بغیر براہ راست فنکشن باڈی میں ڈال دیا۔ اسپیسیفیکیشن میں یہ نہیں کہا گیا تھا کہ pageWidth کو آرگیومنٹ لسٹ (argument list) کے ذریعے آنا چاہیے۔ اس میں یہ بھی نہیں کہا گیا تھا کہ فنکشن ماڈیول اسکوپ پر موجود ہے، جہاں pageWidth اب نظر نہیں آتا۔ اس نے محض یہ فرض کر لیا کہ نافذ کرنے والا (implementer) ایگزیکیوشن کانٹیکسٹ (execution context) کو ضمناً سمجھتا ہے۔
اس کا نتیجہ ہر PDF لوڈ پر ReferenceError کی صورت میں نکلا۔ چونکہ وہ ویری ایبل ماڈیول اسکوپ میں موجود نہیں تھا، اس لیے فنکشن فوری طور پر ایرر دے گیا۔ اگر اسپیسیفیکیشن میں فنکشن کی حد (boundary) اور اس کے ان پٹس کا واضح طور پر نام لیا جاتا، تو ڈویلپر pageWidth کو پاس کرتا، اور یہ بگ ساختی طور پر ناممکن ہوتا۔ اس کے بجائے، ہدایت ایک جال کی طرح کام کر رہی تھی، جو نافذ کرنے والے کو ایک ایسے پیرنٹ اسکوپ تک پہنچنے کی دعوت دے رہی تھی جو موجود ہی نہیں تھا۔
"Uses" کے چار معنی
اصل مسئلہ یہ ہے کہ نثر (prose) میں ٹائپ سسٹم (type system) کی کمی ہوتی ہے۔ جب ایک اسپیسیفیکیشن کہتی ہے کہ "فنکشن X کو استعمال کرتا ہے (uses X)"، تو ایک جدید JavaScript کوڈ بیس میں یہ جملہ کم از کم چار مخصوص طریقوں سے مبہم ہوتا ہے:
- فنکشن X کو ایک فارمل پیرامیٹر کے طور پر وصول کرتا ہے۔
- فنکشن اسی فائل میں اعلان کردہ ماڈیول لیول ویری ایبل سے X کو پڑھتا ہے۔
- فنکشن ایک نییسٹڈ پیرنٹ اسکوپ سے X کو کلوزر (close over) کے ذریعے حاصل کرتا ہے۔
- فنکشن اسے پاس کیے گئے ایک بڑے آبجیکٹ سے X کو نکالتا ہے۔
ان میں سے ہر آپشن اسپیسیفیکیشن کے الفاظ پر پورا اترتا ہے۔ ان میں سے ہر ایک اسٹیٹک اینالیسس (static analysis) پاس کر لیتا ہے۔ لیکن کسی دی گئی حد (boundary) کے لیے ان میں سے صرف ایک ہی درست ہوتا ہے، اور غلط انتخاب اس حد کے پار مفروضوں کو اس طرح لیک کر دیتا ہے جو خاموشی سے کمپائل ہو جاتے ہیں۔
ڈویلپرز عام طور پر لکھتے وقت سب سے آسان راستہ چنتے ہیں۔ اگر pageWidth کسی بیرونی اسکوپ میں موجود ہو، تو وہ فنکشن سگنیچر (function signature) کو تبدیل کرنے کے بجائے وہیں سے اسے پڑھ لیں گے۔ ایک کلوزر (closure) اس انحصار کو چھپا دیتا ہے۔ کوڈ پہلی بار چلتا ہے، ٹیسٹ سوٹ پاس کرتا ہے، اور شپ ہو جاتا ہے۔ ہفتوں بعد، کوئی اس فنکشن کو دوبارہ استعمال کے لیے یا پڑھنے میں آسانی کے لیے کسی دوسری فائل میں منتقل کرتا ہے۔ پیرنٹ اسکوپ غائب ہو جاتا ہے۔ کوڈ ٹوٹ جاتا ہے، اور یہ ٹوٹ پھوٹ ایک نئی ریگریشن (regression) کی طرح نظر آتی ہے حالانکہ اصل وجہ وہی اصل چھپا ہوا انحصار تھا۔
ویب ورکرز ثبوت مٹا دیتے ہیں
یہ مسئلہ اس وقت واقعی سنگین ہو جاتا ہے جب آرکیٹیکچر میں ویب ورکرز (Web Workers) شامل ہو جاتے ہیں۔ جب کسی ورکر کے اندر کوئی ایرر آتا ہے، تو براؤزر اس معلومات کو ہٹا دیتا ہے جس کی آپ کو سب سے زیادہ ضرورت ہوتی ہے۔
اصل میں جو ہوتا ہے وہ یہ ہے: ورکر کے اندر، ایک ان کیچڈ ایکسیپشن (uncaught exception) ایک ErrorEvent فائر کرتی ہے۔ اگر ورکر اس ایرر کو مین تھریڈ (main thread) کو آگے بھیجتا ہے، تو عام طریقہ یہ ہوتا ہے کہ message اسٹرنگ کو پکڑ کر اس حد کے پار بھیج دیا جائے۔ مین تھریڈ وہ اسٹرنگ وصول کرتا ہے، اس سے ایک نیا Error آبجیکٹ بناتا ہے، اور اسے لاگ یا ری تھرو (re-throw) کرتا ہے۔ DevTools میں جو نظر آتا ہے وہ مین تھریڈ کے میسج ہینڈلر کے اندر دوبارہ بنایا گیا ایرر ہوتا ہے۔ اصل فائل کا نام، لائن نمبر، اور اسٹیک ٹریس ضائع ہو جاتے ہیں۔ ناکامی کا اصل مقام ناقابلِ دید ہو جاتا ہے۔
چنانچہ جب غائب شدہ pageWidth نے ورکر کے اندر ReferenceError پیدا کیا، تو مین تھریڈ نے اس جگہ پر صرف یہ ٹیکسٹ رپورٹ کیا جہاں میسج ہینڈل کیا گیا تھا: "pageWidth is not defined"۔ اصل ماڈیول لیول فنکشن تو...
