ప్రతి PDF లోడ్ కూడా ఒకే రకమైన ReferenceError తో క్రాష్ అయింది. స్టాక్ ట్రేస్ (stack trace) ఎక్కడా ఉపయోగపడేలా లేదు, మరియు కోడ్‌ను నడిపించే స్పెసిఫికేషన్ (specification) కాగితం మీద చూస్తే చాలా సహేతుకంగా అనిపించింది. అది ఒక ఫీచర్ ఫ్లాగ్‌ను (feature flag) తనిఖీ చేయాలని, మరియు ప్రతి రీజియన్‌ను pageWidth లో సగం విలువతో పోల్చాలని చెప్పింది. అది ఏమి జరగాలి అనే దానిని వివరించింది. కానీ pageWidth అనేది ఎక్కడి నుండి రావాలి అనే దానిని వివరించడంలో విఫలమైంది, మరియు ఆ ఒక్క లోపం మొత్తం పైప్‌లైన్‌ను దెబ్బతీయడానికి సరిపోతుంది.

ఆర్కిటెక్చర్ డాక్యుమెంట్లు ప్రవర్తనను (behavior) వివరించడంలో బాగుంటాయి. కానీ బౌండరీలను (boundaries) వివరించడంలో అవి తరచుగా విఫలమవుతాయి. "ఫంక్షన్ xను pageWidthతో పోల్చి చూస్తుంది" అని చెప్పే ఒక వాక్యం సాంకేతిక ఒప్పందం (technical contract) కాదు; అది సాధారణ ఇంగ్లీష్‌లో ఒక డిపెండెన్సీని దాచిపెట్టే ఒక కథనం (narrative) మాత్రమే. ఒక డెవలపర్ ఆ వాక్యాన్ని చదివి, pageWidth అనే పేరును రిఫరెన్స్ చేస్తూ ఒక మాడ్యూల్-లెవల్ ఫంక్షన్‌ను రాసినప్పుడు, ఆ కోడ్ వివరణకు అనుగుణంగా ఉన్నందున సరిగ్గా ఉన్నట్లు కనిపిస్తుంది. ఆ తర్వాత రన్‌టైమ్ (runtime) ఆ పేరును వెతికినప్పుడు, స్కోప్‌లో ఏమీ దొరకక, ఎర్రర్‌ను విసురుతుంది.

సరళంగా ఉండాల్సిన రీఫ్యాక్టర్ (Refactor)

పేజీ అసెంబ్లీ రీఫ్యాక్టర్ సమయంలో నేను సరిగ్గా ఇదే ప్యాటర్న్‌ను చూశాను. స్పెసిఫికేషన్‌లో రెండు స్పష్టమైన అవసరాలు ఉన్నాయి:

  • FEATURE_LAYOUT ను తనిఖీ చేయాలి.
  • ప్రతి రీజియన్‌ను pageWidth / 2 తో పోల్చాలి.

డెవలపర్ ఆ సూచనలను యథాతథంగా పాటించారు. వారు మాడ్యూల్ స్కోప్‌లో ఒక యుటిలిటీ ఫంక్షన్‌ను సృష్టించి, pageWidth ను పారామీటర్‌గా పేర్కొనకుండా నేరుగా ఫంక్షన్ బాడీలోకి వేశారు. pageWidth అనేది ఆర్గ్యుమెంట్ లిస్ట్ (argument list) ద్వారా రావాలని స్పెసిఫికేషన్ పేర్కొనలేదు. ఫంక్షన్ మాడ్యూల్ స్కోప్‌లో ఉందని, అక్కడ pageWidth కనిపించదని కూడా పేర్కొనలేదు. అమలు చేసే వ్యక్తికి ఎగ్జిక్యూషన్ కాంటెక్స్ట్ (execution context) గురించి ముందే తెలుసని అది ఊహించింది.

దీని ఫలితంగా ప్రతి PDF లోడ్ సమయంలో ReferenceError వచ్చింది. ఆ వేరియబుల్ మాడ్యూల్ స్కోప్‌లో లేకపోవడం వల్ల, ఫంక్షన్ వెంటనే ఎర్రర్‌ను ఇచ్చింది. ఒకవేళ స్పెసిఫికేషన్ ఫంక్షన్ బౌండరీని మరియు దాని ఇన్‌పుట్‌లను స్పష్టంగా పేర్కొని ఉంటే, డెవలపర్ pageWidth ను పారామీటర్‌గా పంపేవారు, అప్పుడు ఆ బగ్ రావడం అసాధ్యమయ్యేది. దానికి బదులుగా, ఆ సూచన ఒక ఉచ్చులా పనిచేసి, అమలు చేసే వ్యక్తిని లేని పేరెంట్ స్కోప్ వైపు వెళ్లేలా ప్రేరేపించింది.

"Uses" అనే పదానికి ఉన్న నాలుగు అర్థాలు

అసలు సమస్య ఏమిటంటే, వచనం (prose) లో టైప్ సిస్టమ్ (type system) ఉండదు. ఒక స్పెసిఫికేషన్ "ఫంక్షన్ Xను ఉపయోగిస్తుంది (uses X)" అని చెప్పినప్పుడు, ఆధునిక JavaScript కోడ్‌బేస్‌లో ఆ వాక్యం కనీసం నాలుగు రకాలుగా అస్పష్టంగా ఉంటుంది:

  • ఫంక్షన్ Xను ఒక ఫార్మల్ పారామీటర్‌గా స్వీకరిస్తుంది.
  • ఫంక్షన్ అదే ఫైల్‌లో డిక్లేర్ చేయబడిన మాడ్యూల్-లెవల్ వేరియబుల్ నుండి Xను చదువుతుంది.
  • ఫంక్షన్ ఒక నెస్టెడ్ పేరెంట్ స్కోప్ నుండి Xను క్లోజర్ (closure) ద్వారా తీసుకుంటుంది.
  • ఫంక్షన్‌కు పంపబడిన ఒక పెద్ద ఆబ్జెక్ట్ నుండి ఫంక్షన్ Xను సంగ్రహిస్తుంది.

ఈ ఆప్షన్లన్నీ స్పెసిఫికేషన్ యొక్క పదజాలాన్ని సంతృప్తిపరుస్తాయి. ఇవన్నీ స్టాటిక్ అనాలిసిస్‌లో (static analysis) కూడా ఉత్తీర్ణమవుతాయి. కానీ ఒక నిర్దిష్ట బౌండరీకి కేవలం ఒకటి మాత్రమే సరైనది, మరియు తప్పు ఎంపిక ఆ బౌండరీని దాటి ఊహలను నిశ్శబ్దంగా వ్యాపింపజేస్తుంది.

డెవలపర్లు సాధారణంగా రాసే సమయంలో అత్యంత సులభమైన మార్గాన్ని ఎంచుకుంటారు. ఒకవేళ pageWidth అనేది బయటి స్కోప్‌లో ఉంటే, వారు ఫంక్షన్ సిగ్నేచర్‌ను మార్చడానికి బదులుగా అక్కడి నుండి దానిని చదువుతారు. ఒక క్లోజర్ ఆ డిపెండెన్సీని దాచిపెడుతుంది. కోడ్ మొదటిసారి నడుస్తుంది, టెస్ట్ సూట్‌లను పాస్ అవుతుంది మరియు షిప్ అవుతుంది. వారాల తర్వాత, ఎవరైనా ఆ ఫంక్షన్‌ను మళ్ళీ ఉపయోగించడానికి లేదా చదవడానికి సులభంగా ఉండటం కోసం వేరే ఫైల్‌కు మారుస్తారు. అప్పుడు పేరెంట్ స్కోప్ మాయమవుతుంది. కోడ్ విఫలమవుతుంది, మరియు మూల కారణం అసలు దాగి ఉన్న డిపెండెన్సీ అయినప్పటికీ, ఆ విఫలమవ్వడం ఒక కొత్త రిగ్రెషన్ (regression) లాగా కనిపిస్తుంది.

వెబ్ వర్కర్లు (Web Workers) ఆధారాలను తుడిచివేస్తాయి

ఆర్కిటెక్చర్‌లో వెబ్ వర్కర్లు (Web Workers) ప్రవేశించిన తర్వాత ఈ సమస్య మరింత తీవ్రమవుతుంది. ఒక వర్కర్ లోపల ఎర్రర్ వచ్చినప్పుడు, బ్రౌజర్ మీకు అత్యంత అవసరమైన సమాచారాన్ని తొలగిస్తుంది.

అసలు ఏం జరుగుతుందంటే. ఒక వర్కర్ లోపల, అన్‌క్యాచ్ ఎక్సెప్షన్ (uncaught exception) ఒక ErrorEventను ఫైర్ చేస్తుంది. ఒకవేళ వర్కర్ ఆ ఎర్రర్‌ను మెయిన్ థ్రెడ్‌కు పంపితే, సాధారణ పద్ధతి ఏమిటంటే message స్ట్రింగ్‌ను తీసుకుని బౌండరీ దాటి పంపడం. మెయిన్ థ్రెడ్ ఆ స్ట్రింగ్‌ను అందుకుని, దాని నుండి కొత్త Error ఆబ్జెక్ట్‌ను నిర్మించి, దానిని లాగ్ చేస్తుంది లేదా మళ్ళీ త్రో చేస్తుంది. DevToolsలో కనిపించేది మెయిన్ థ్రెడ్ యొక్క మెసేజ్ హ్యాండ్లర్‌లో పునర్నిర్మించబడిన ఎర్రర్ మాత్రమే. అసలైన ఫైల్ పేరు, లైన్ నంబర్ మరియు స్టాక్ ట్రేస్ (stack trace) తొలగించబడతాయి. వైఫల్యం జరిగిన అసలు ప్రదేశం అదృశ్యమవుతుంది.

కాబట్టి వర్కర్ లోపల pageWidth లేకపోవడం వల్ల ReferenceError వచ్చినప్పుడు, మెయిన్ థ్రెడ్ కేవలం "pageWidth is not defined" అనే టెక్స్ట్‌ను మాత్రమే మెసేజ్ హ్యాండ్లర్ వద్ద రిపోర్ట్ చేసింది. అసలైన మాడ్యూల్-లెవల్ ఫంక్షన్...