ഓരോ PDF ലോഡ് ചെയ്യുമ്പോഴും ഒരേ ReferenceError കാരണം ക്രാഷ് സംഭവിക്കുന്നു. സ്റ്റാക്ക് ട്രാസ് (stack trace) ഉപയോഗപ്രദമായ ഒരിടത്തേക്കും വിരൽ ചൂണ്ടുന്നില്ല, കൂടാതെ കോഡിന് വഴികാട്ടിയ സ്പെസിഫിക്കേഷനും പേപ്പറിൽ നോക്കുമ്പോൾ തികച്ചും യുക്തിസഹമായി തോന്നിയിരുന്നു. ഒരു ഫീച്ചർ ഫ്ലാഗ് (feature flag) പരിശോധിക്കാനും, ഓരോ റീജിയണും pageWidth-ന്റെ പകുതിയുമായി താരതമ്യം ചെയ്യാനും അത് നിർദ്ദേശിച്ചു. എന്താണ് സംഭവിക്കേണ്ടതെന്ന് അത് വിവരിച്ചു. എന്നാൽ pageWidth എവിടെ നിന്ന് വരണം എന്നതിനെക്കുറിച്ച് അത് പറഞ്ഞില്ല, ആ ഒരു കുറവ് മതിയായിരുന്നു മുഴുവൻ പൈപ്പ്‌ലൈനും തകരാൻ.

ആർക്കിടെക്ചർ ഡോക്യുമെന്റുകൾ പെരുമാറ്റരീതികൾ (behavior) വിശദീകരിക്കുന്നതിൽ മികച്ചതാണ്. എന്നാൽ അവ അതിരുകൾ (boundaries) വിശദീകരിക്കുന്നതിൽ പലപ്പോഴും പരാജയപ്പെടുന്നു. "ഫംഗ്ഷൻ x-നെ pageWidth-മായി താരതമ്യം ചെയ്യുന്നു" എന്ന് പറയുന്ന ഒരു വാചകം ഒരു സാങ്കേതിക കരാറല്ല (technical contract); അത് ലളിതമായ ഇംഗ്ലീഷിൽ ഒരു ഡിപെൻഡൻസിയെ (dependency) ഒളിപ്പിച്ചു വെക്കുന്ന ഒരു വിവരണമാണ്. ഒരു ഡെവലപ്പർ ആ വാചകം വായിക്കുകയും pageWidth എന്ന പേരിൽ റഫറൻസ് ചെയ്യുന്ന ഒരു മോഡ്യൂൾ ലെവൽ ഫംഗ്ഷൻ എഴുതുകയും ചെയ്യുമ്പോൾ, അത് വിവരണത്തിന് അനുസൃതമായതുകൊണ്ട് കോഡ് ശരിയാണെന്ന് തോന്നും. പിന്നീട് റൺടൈം (runtime) ആ പേര് കണ്ടെത്താൻ ശ്രമിക്കുമ്പോൾ, സ്കോപ്പിൽ (scope) ഒന്നും കണ്ടെത്താതെ എറർ കാണിക്കുന്നു.

ലളിതമായിരിക്കേണ്ടിയിരുന്ന ആ റീഫാക്ടർ (Refactor)

ഒരു പേജ് അസംബ്ലി റീഫാക്ടറിനിടെ (page assembly refactor) ഞാൻ ഇതേ രീതി കണ്ടു. സ്പെസിഫിക്കേഷനിൽ രണ്ട് വ്യക്തമായ ആവശ്യകതകൾ നൽകിയിരുന്നു:

  • FEATURE_LAYOUT പരിശോധിക്കുക.
  • ഓരോ റീജിയണും pageWidth / 2-മായി താരതമ്യം ചെയ്യുക.

ഡെവലപ്പർ നിർദ്ദേശങ്ങൾ കൃത്യമായി പാലിച്ചു. അവർ മോഡ്യൂൾ സ്കോപ്പിൽ (module scope) ഒരു യൂട്ടിലിറ്റി ഫംഗ്ഷൻ നിർമ്മിക്കുകയും pageWidth-നെ ഒരു പാരാമീറ്ററായി (parameter) നൽകുന്നതിന് പകരം നേരിട്ട് ഫംഗ്ഷൻ ബോഡിയിൽ ഉപയോഗിക്കുകയും ചെയ്തു. pageWidth ഒരു ആർഗ്യുമെന്റ് ലിസ്റ്റിലൂടെ (argument list) വരണമെന്ന് സ്പെസിഫിക്കേഷൻ പറഞ്ഞിരുന്നില്ല. ഫംഗ്ഷൻ മോഡ്യൂൾ സ്കോപ്പിലാണ് ഇരിക്കുന്നതെന്നും അവിടെ pageWidth കാണാൻ കഴിയില്ലെന്നും അത് പറഞ്ഞിരുന്നില്ല. നടപ്പിലാക്കുന്നയാൾക്ക് എക്സിക്യൂഷൻ കോൺടെക്സ്റ്റ് (execution context) കൃത്യമായി അറിയാമെന്ന് അത് അനുമാനിച്ചു.

ഇതിന്റെ ഫലമായി ഓരോ PDF ലോഡ് ചെയ്യുമ്പോഴും ഒരു ReferenceError ഉണ്ടായി. വേരിയബിൾ മോഡ്യൂൾ സ്കോപ്പിൽ ഇല്ലാതിരുന്നതിനാൽ ഫംഗ്ഷൻ ഉടൻ തന്നെ എറർ കാണിച്ചു. സ്പെസിഫിക്കേഷൻ ഫംഗ്ഷന്റെ അതിരുകളെയും (boundary) അതിന്റെ ഇൻപുട്ടുകളെയും വ്യക്തമായി പറഞ്ഞിരുന്നെങ്കിൽ, ഡെവലപ്പർ pageWidth പാസ്സ് ചെയ്യുമായിരുന്നു, അങ്ങനെ ആ ബഗ്ഗ് ഒഴിവാക്കാമായിരുന്നു. പകരം, ആ നിർദ്ദേശം നിലവിലില്ലാത്ത ഒരു പാരന്റ് സ്കോപ്പിലേക്ക് (parent scope) എത്തിച്ചേരാൻ പ്രേരിപ്പിക്കുന്ന ഒരു കെണിയായി മാറി.

"Uses" എന്നതിന്റെ നാല് അർത്ഥങ്ങൾ

ഇതിലെ ആഴത്തിലുള്ള പ്രശ്നം എന്നത് വിവരണങ്ങളിൽ (prose) ഒരു ടൈപ്പ് സിസ്റ്റം (type system) ഇല്ല എന്നതാണ്. ഒരു സ്പെസിഫിക്കേഷൻ "ഫംഗ്ഷൻ X ഉപയോഗിക്കുന്നു" എന്ന് പറയുമ്പോൾ, ആ വാചകം ആധുനികമായ ഒരു JavaScript കോഡ്ബേസിൽ (codebase) കുറഞ്ഞത് നാല് രീതിയിൽ അവ്യക്തമാണ്:

  • ഫംഗ്ഷൻ X-നെ ഒരു ഫോർമൽ പാരാമീറ്ററായി (formal parameter) സ്വീകരിക്കുന്നു.
  • ഒരേ ഫയലിൽ തന്നെ ഡിക്ലയർ ചെയ്തിട്ടുള്ള ഒരു മോഡ്യൂൾ ലെവൽ വേരിയബിളിൽ നിന്ന് ഫംഗ്ഷൻ X വായിക്കുന്നു.
  • ഒരു നെസ്റ്റഡ് പാരന്റ് സ്കോപ്പിൽ (nested parent scope) നിന്ന് ഫംഗ്ഷൻ X-നെ ക്ലോസ് ചെയ്യുന്നു (closes over).
  • അതിലേക്ക് പാസ്സ് ചെയ്ത ഒരു വലിയ ഒബ്‌ജക്റ്റിൽ നിന്ന് ഫംഗ്ഷൻ X വേർതിരിച്ചെടുക്കുന്നു.

ഈ ഓപ്ഷനുകളെല്ലാം തന്നെ സ്പെസിഫിക്കേഷനിലെ വാക്കുകൾക്ക് അനുസൃതമാണ്. ഇവയെല്ലാം സ്റ്റാറ്റിക് അനാലിസിസിൽ (static analysis) വിജയിക്കുകയും ചെയ്യും. എന്നാൽ ഒരു നിശ്ചിത അതിർത്തിക്ക് (boundary) അനുയോജ്യമായത് ഇതിൽ ഒന്ന് മാത്രമായിരിക്കും. തെറ്റായ തിരഞ്ഞെടുപ്പ് ആ അതിർത്തിക്ക് അപ്പുറം അറിവില്ലാതെ തന്നെ തെറ്റായ അനുമാനങ്ങൾ (assumptions) കടത്തിവിടുന്നു.

ഡെവലപ്പർമാർ സാധാരണയായി എഴുതുന്ന സമയത്ത് ഏറ്റവും എളുപ്പമുള്ള വഴി തിരഞ്ഞെടുക്കുന്നു. pageWidth ഒരു ഔട്ടർ സ്കോപ്പിൽ (outer scope) ഉണ്ടെങ്കിൽ, ഒരു ഫംഗ്ഷൻ സിഗ്നേച്ചർ (function signature) മാറ്റുന്നതിന് പകരം അവർ അവിടെ നിന്ന് അത് വായിക്കും. ഒരു ക്ലോഷർ (closure) ആ ഡിപെൻഡൻസിയെ ഒളിപ്പിച്ചു വെക്കുന്നു. കോഡ് ആദ്യത്തെ തവണ പ്രവർത്തിക്കുമ്പോൾ ശരിയായി പ്രവർത്തിക്കുന്നു, ടെസ്റ്റുകൾ പാസ്സാകുന്നു, പ്രൊഡക്ഷനിലേക്ക് അയക്കുന്നു. ആഴ്ചകൾക്ക് ശേഷം, ആരെങ്കിലും ആ ഫംഗ്ഷൻ വീണ്ടും ഉപയോഗിക്കാനോ അല്ലെങ്കിൽ വായനാസുഖത്തിനായി (readability) മറ്റൊരു ഫയലിലേക്ക് മാറ്റുകയോ ചെയ്യുന്നു. അപ്പോൾ പാരന്റ് സ്കോപ്പ് ഇല്ലാതാകുന്നു. കോഡ് തകരുന്നു, ആ തകർച്ച ഒരു പുതിയ റഗ്രഷൻ (regression) ആണെന്ന് തോന്നുമെങ്കിലും അതിന്റെ യഥാർത്ഥ കാരണം ആ ഒളിഞ്ഞിരുന്ന ഡിപെൻഡൻസിയാണ്.

വെബ് വർക്കറുകൾ തെളിവുകൾ മായ്ച്ചുകളയുന്നു

ആർക്കിടെക്ചറിൽ വെബ് വർക്കറുകൾ (Web Workers) വരുമ്പോൾ ഈ പ്രശ്നം കൂടുതൽ സങ്കീർണ്ണമാകുന്നു. ഒരു വർക്കറിനുള്ളിൽ ഒരു എറർ സംഭവിക്കുമ്പോൾ, നിങ്ങൾക്ക് ഏറ്റവും ആവശ്യമുള്ള വിവരങ്ങൾ ബ്രൗസർ നീക്കം ചെയ്യുന്നു.

യഥാർത്ഥത്തിൽ എന്താണ് സംഭവിക്കുന്നത് എന്ന് നോക്കാം. ഒരു വർക്കറിനുള്ളിൽ, ഒരു അൺകാറ്റ് എക്സെപ്ഷൻ (uncaught exception) സംഭവിക്കുമ്പോൾ അത് ഒരു ErrorEvent പുറപ്പെടുവിക്കുന്നു. വർക്കർ ആ എറർ മെയിൻ ത്രെഡിലേക്ക് (main thread) കൈമാറുന്നുണ്ടെങ്കിൽ, സാധാരണയായി message സ്ട്രിംഗ് എടുത്ത് അതിനെ അതിർത്തി കടത്തി അയക്കുകയാണ് പതിവ്. മെയിൻ ത്രെഡ് ആ സ്ട്രിംഗ് സ്വീകരിക്കുന്നു, അതിൽ നിന്ന് പുതിയൊരു Error ഒബ്‌ജക്റ്റ് നിർമ്മിക്കുന്നു, അത് ലോഗ് ചെയ്യുന്നു അല്ലെങ്കിൽ വീണ്ടും എറർ കാണിക്കുന്നു. ഡെവ് ടൂൾസിൽ (DevTools) കാണപ്പെടുന്നത് മെയിൻ ത്രെഡിലെ മെസ്സേജ് ഹാൻഡ്‌ലറിലുള്ള ആ പുനർനിർമ്മിച്ച എറർ ആണ്. യഥാർത്ഥ ഫയൽ നാമം, ലൈൻ നമ്പർ, സ്റ്റാക്ക് ട്രാസ് എന്നിവ ഒഴിവാക്കപ്പെടുന്നു. പരാജയപ്പെട്ട യഥാർത്ഥ സ്ഥാനം അദൃശ്യമാകുന്നു.

അതുകൊണ്ട്, വർക്കറിനുള്ളിൽ pageWidth ഇല്ലാത്തതിനാൽ ഒരു ReferenceError ഉണ്ടായപ്പോൾ, മെയിൻ ത്രെഡ് റിപ്പോർട്ട് ചെയ്തത് മെസ്സേജ് കൈകാര്യം ചെയ്ത സ്ഥലത്ത് വെറും "pageWidth is not defined" എന്ന ടെക്സ്റ്റ് മാത്രമാണ്. യഥാർത്ഥ മോഡ്യൂൾ ലെവൽ ഫംഗ്ഷൻ ഇരുന്ന...