ஒவ்வொரு PDF பதிவும் அதே ReferenceError-உடன் செயலிழந்தது. stack trace எந்தப் பயனுள்ள இடத்தையும் சுட்டிக்காட்டவில்லை, மேலும் குறியீட்டை வழிநடத்திய விவரக்குறிப்பு (specification) காகிதத்தில் பார்க்கும்போது முற்றிலும் நியாயமானதாகத் தெரிந்தது. அது ஒரு feature flag-ஐச் சரிபார்க்கச் சொன்னது, மேலும் ஒவ்வொரு பகுதியையும் (region) pageWidth-இன் பாதியுடன் ஒப்பிடச் சொன்னது. என்ன நடக்க வேண்டும் என்பதை அது விவரித்தது. ஆனால் pageWidth எங்கிருந்து வர வேண்டும் என்பதை விவரிக்கத் தவறிவிட்டது, அந்த ஒரு விடுபடல் முழுமையான செயல்பாட்டு வரிசையையும் (pipeline) முடக்குவதற்குப் போதுமானதாக இருந்தது.
கட்டமைப்பு ஆவணங்கள் (Architecture documents) செயல்பாடுகளை விளக்குவதில் சிறந்தவை. ஆனால் எல்லைகளை (boundaries) விளக்குவதில் அவை பெரும்பாலும் மோசமானவை. "சார்பு (function) x-ஐ pageWidth உடன் சரிபார்க்கிறது" என்று சொல்லும் ஒரு வாக்கியம் ஒரு தொழில்நுட்ப ஒப்பந்தம் அல்ல; அது சாதாரண ஆங்கிலத்திற்குள் ஒரு சார்புத் தன்மையை (dependency) மறைத்து வைக்கும் ஒரு கதை சொல்லல் மட்டுமே. ஒரு டெவலப்பர் அந்த வாக்கியத்தைப் படித்துவிட்டு, pageWidth என்ற பெயரைப் பயன்படுத்தும் ஒரு module-level function-ஐ எழுதும்போது, அந்த குறியீடு விவரிப்பைப் பூர்த்தி செய்வதால் சரியாகத் தோன்றும். பின்னர் runtime அந்தப் பெயரைத் தேடும்போது, அதன் எல்லைக்குள் (scope) எதையும் காணாமல், பிழையைத் தூண்டுகிறது.
எளிமையாக இருந்திருக்க வேண்டிய அந்த மாற்றியமைப்பு (Refactor)
ஒரு பக்கத் தொகுப்பு மாற்றியமைப்பின் (page assembly refactor) போது இதே போன்ற ஒரு முறையை நான் கண்டேன். விவரக்குறிப்பு இரண்டு தெளிவான தேவைகளைப் பட்டியலிட்டது:
FEATURE_LAYOUT-ஐச் சரிபார்க்கவும்.- ஒவ்வொரு பகுதியையும்
pageWidth / 2-உடன் ஒப்பிடவும்.
டெவலப்பர் அந்த அறிவுறுத்தல்களை அப்படியே பின்பற்றினார். அவர் ஒரு utility function-ஐ module scope-இல் பிரித்தெடுத்தார் மற்றும் pageWidth-ஐ ஒரு அளவுருவாக (parameter) குறிப்பிடாமல் நேரடியாக function body-க்குள் இறக்கினார். pageWidth ஒரு argument list மூலம் வர வேண்டும் என்று விவரக்குறிப்பு கூறவில்லை. அந்த function module scope-இல் உள்ளது என்றும், அங்கு pageWidth புலப்படாது என்றும் அது கூறவில்லை. அதைச் செயல்படுத்தும் நபர், செயல்பாட்டு சூழலை (execution context) இயல்பாகவே புரிந்துகொள்வார் என்று அது ஊகித்தது.
இதன் விளைவாக ஒவ்வொரு PDF பதிவிலும் ஒரு ReferenceError ஏற்பட்டது. அந்த மாறி module scope-இல் இல்லாததால், function உடனடியாகப் பிழையைத் தூண்டியது. விவரக்குறிப்பு அந்த function-இன் எல்லை மற்றும் அதன் உள்ளீடுகளை (inputs) தெளிவாகக் குறிப்பிட்டிருந்தால், டெவலப்பர் pageWidth-ஐ உள்ளீடாக அனுப்பியிருப்பார், மேலும் அந்தப் பிழை கட்டமைப்பிலேயே சாத்தியமற்றதாக இருந்திருக்கும். அதற்குப் பதிலாக, அந்த அறிவுறுத்தல் ஒரு பொறி போலச் செயல்பட்டு, இல்லாத ஒரு parent scope-க்குள் நுழையச் செயல்படுத்தும் நபரைத் தூண்டியது.
"Uses" என்பதன் நான்கு பொருள்கள்
ஆழமான பிரச்சனை என்னவென்றால், விளக்க உரையில் (prose) ஒரு வகை அமைப்பு (type system) இல்லை. ஒரு விவரக்குறிப்பு "சார்பு X-ஐப் பயன்படுத்துகிறது" என்று சொல்லும்போது, நவீன JavaScript codebase-இல் அந்த வாக்கியம் குறைந்தது நான்கு வழிகளில் தெளிவற்றதாக உள்ளது:
- சார்பு (function) X-ஐ ஒரு முறையான அளவுருவாக (formal parameter) பெறுகிறது.
- சார்பு, அதே கோப்பில் அறிவிக்கப்பட்ட ஒரு module-level மாறியிலிருந்து X-ஐப் படிக்கிறது.
- சார்பு, ஒரு உட்பொதிக்கப்பட்ட பெற்றோர் எல்லையிலிருந்து (nested parent scope) X-ஐ மூடுகிறது (closes over).
- சார்பு, அதற்கு அனுப்பப்பட்ட ஒரு பெரிய பொருளிலிருந்து (object) X-ஐப் பிரித்தெடுக்கிறது.
இந்த விருப்பங்கள் ஒவ்வொன்றும் விவரக்குறிப்பின் (spec) வார்த்தைகளை நிறைவு செய்கின்றன. இவை ஒவ்வொன்றும் static analysis சோதனையில் தேர்ச்சி பெறுகின்றன. ஆனால் ஒரு குறிப்பிட்ட எல்லைக்கு (boundary) ஒன்று மட்டுமே சரியானது, மேலும் தவறான தேர்வு அந்த எல்லையைத் தாண்டி ஊகங்களைச் சத்தமில்லாமல் கசியவிடுகிறது.
டெவலப்பர்கள் பொதுவாக எழுதும் போது மிக எளிதான வழியையே தேர்ந்தெடுப்பார்கள். pageWidth ஒரு outer scope-இல் இருந்தால், அவர்கள் ஒரு function signature-ஐ மாற்றுவதற்குப் பதிலாக அங்கிருந்து அதைப் படிக்கவே விரும்புவார்கள். ஒரு closure அந்தச் சார்புத் தன்மையை மறைத்துவிடுகிறது. குறியீடு முதல் முறை இயங்கும்போது வேலை செய்கிறது, சோதனைகளைத் தாண்டிச் செல்கிறது, மற்றும் பயன்பாட்டிற்கு அனுப்பப்படுகிறது. வாரங்களுக்குப் பிறகு, யாரோ அந்த அதே function-ஐ மறுபயன்பாட்டிற்காக அல்லது வாசிப்புத் திறனை மேம்படுத்த வேறொரு கோப்பிற்கு நகர்த்துகிறார்கள். அப்போது அந்த parent scope மறைந்துவிடுகிறது. குறியீடு உடைந்துவிடுகிறது, மேலும் மூலக் காரணம் அந்த ஆரம்பகால மறைக்கப்பட்ட சார்புத் தன்மைதான் என்றாலும், அந்த உடைப்பு ஒரு புதிய பின்னடைவு (regression) போலத் தோன்றுகிறது.
Web Workers ஆதாரங்களை அழித்துவிடுகின்றன
Web Workers கட்டமைப்பிற்குள் நுழையும்போது இந்தப் பிரச்சனை உண்மையிலேயே கொடூரமானதாக மாறுகிறது. ஒரு worker-க்குள் பிழை ஏற்படும்போது, உலாவியே (browser) உங்களுக்குத் தேவையான தகவல்களை நீக்கிவிடுகிறது.
உண்மையில் என்ன நடக்கிறது என்றால்: ஒரு worker-க்குள், பிடிக்கப்படாத ஒரு விதிவிலக்கு (uncaught exception) ஒரு ErrorEvent-ஐத் தூண்டுகிறது. அந்த worker அந்தப் பிழையை main thread-க்கு அனுப்பினால், வழக்கமான முறை என்பது message சரத்தைப் (string) பிடித்து அந்த எல்லையைத் தாண்டி அனுப்புவதாகும். main thread அந்தச் சரத்தைப் பெற்று, அதிலிருந்து ஒரு புதிய Error பொருளை உருவாக்கி, அதை log செய்கிறது அல்லது மீண்டும் தூண்டுகிறது. DevTools-இல் தெரிவது main thread-இன் message handler-க்குள் மறுசீரமைக்கப்பட்ட பிழை மட்டுமே. அசல் கோப்பின் பெயர், வரி எண் மற்றும் stack trace ஆகியவை கைவிடப்படுகின்றன. பிழை ஏற்பட்ட உண்மையான இடம் கண்ணுக்குத் தெரியாமல் மறைந்துவிடுகிறது.
எனவே, விடுபட்ட pageWidth worker-க்குள் ஒரு ReferenceError-ஐத் தூண்டியபோது, அந்தச் செய்தி கையாளப்பட்ட இடத்தில் "pageWidth is not defined" என்ற உரையை மட்டுமே main thread அறிக்கையிட்டது. உண்மையான module-level function இருந்தது
