Kila upakiaji wa PDF ulianguka kwa ReferenceError ile ile. Stack trace haikuonyesha mahali popote palipo na manufaa, na maelezo (specification) yaliyotumika kuongoza kodi yalionekana kuwa ya mantiki kabisa kwenye karatasi. Yalisema ukague bendera ya kipengele (feature flag), na ulinganishe kila eneo dhidi ya nusu ya pageWidth. Yaliielezea kile kinachopaswa kutokea. Lakini yalishindwa kuelezea pageWidth ilitakiwa itoke wapi, na upungufu huo mmoja ulikuwa wa kutosha kuangusha mfumo (pipeline) mzima.

Nyaraka za usanifu (architecture documents) ni nzuri katika kuelezea tabia. Mara nyingi ni mbaya sana katika kuelezea mipaka (boundaries). Sentensi inayosema "kazi (function) hukagua x dhidi ya pageWidth" si mkataba wa kiufundi; ni simulizi inayoficha utegemezi (dependency) ndani ya Kiingereza cha kawaida. Mtengenezaji anaposoma sentensi hiyo na kuandika kazi ya kiwango cha moduli (module-level function) inayorejelea pageWidth kwa jina, kodi inaonekana kuwa sahihi kwa sababu inakidhi maelezo hayo. Kisha wakati wa utendaji (runtime) unajaribu kutafuta jina hilo, halipati kitu katika upeo (scope), na kutoa kosa (throw error).

Marekebisho (Refactor) Yaliyopaswa Kuwa Rahisi

Niliona mfumo huu ule ule wakati wa marekebisho ya uundaji wa ukurasa (page assembly refactor). Maelezo yaliorodhesha mahitaji mawili yanayoonekana kuwa safi:

  • Kagua FEATURE_LAYOUT.
  • Linganisha kila eneo dhidi ya pageWidth / 2.

Mtengenezaji alifuata maelekezo kwa uaminifu. Walitoa kazi ya msaada (utility function) katika upeo wa moduli (module scope) na kuweka pageWidth moja kwa moja ndani ya mwili wa kazi hiyo bila kuifanya kuwa parameter. Maelezo hayakusema kwamba pageWidth lazima iingie kupitia orodha ya hoja (argument list). Hayakusema kwamba kazi hiyo ilikuwa katika upeo wa moduli, ambapo pageWidth haikuonekana tena. Yalichukulia tu kwamba mtendaji alielewa muktadha wa utendaji (execution context) bila kuhitaji maelezo zaidi.

Matokeo yalikuwa ReferenceError kwenye kila upakiaji wa PDF. Kwa sababu variable haikuwepo katika upeo wa moduli, kazi hiyo ilitoa kosa mara moja. Kama maelezo yangekuwa yametaja wazi mipaka ya kazi na ingizo zake (inputs), mtengenezaji angekuwa amepitisha pageWidth, na hitilafu hiyo ingekuwa haiwezekani kimuundo. Badala yake, maelekezo yalifanya kazi kama mtego, ukimkaribisha mtendaji kutafuta katika upeo wa mzazi (parent scope) ambao haukuwepo.

Maana Nne za "Inatumia"

Tatizo la ndani zaidi ni kwamba maandishi (prose) hayana mfumo wa aina (type system). Maelezo yanaposema "kazi inatumia X," sentensi hiyo ina utata katika angalau njia nne mahususi katika kodi ya kisasa ya JavaScript:

  • Kazi inapokea X kama parameter rasmi.
  • Kazi inasoma X kutoka kwa variable ya kiwango cha moduli iliyotangazwa katika faili moja.
  • Kazi inafunga (closes over) X kutoka kwenye upeo wa mzazi uliopo ndani (nested parent scope).
  • Kazi inatoa X kutoka kwenye object kubwa iliyopitishwa ndani yake.

Kila moja ya chaguzi hizi inakidhi maneno ya maelezo. Kila moja hupita katika uchambuzi wa tuli (static analysis). Lakini ni moja tu kati ya hizo ndiyo iliyo sahihi kwa mpaka fulani, na chaguo lisilo sahihi huchuruzisha dhana katika mpaka huo kwa njia ambayo inajipatia kosa bila kuonyesha (compiles silently).

Watengenezaji kwa kawaida huchagua njia rahisi zaidi wakati wa kuandika. Ikiwa pageWidth inatokea kuwa imekaa katika upeo wa nje, watasoma kutoka hapo badala ya kubadilisha saini ya kazi (function signature). "Closure" huficha utegemezi. Kodi inafanya kazi wakati wa kwanza, inapita katika majaribio (suite), na kusambazwa. Wiki kadhaa baadaye, mtu anahamisha kazi hiyo hiyo kwenda faili tofauti kwa ajili ya kuitumia tena, au kuboresha usomaji. Upeo wa mzazi unapotoweka, kodi inaharibika, na uharibifu huo unaonekana kama hitilafu mpya (regression) ingawa chanzo halisi kilikuwa ule utegemezi wa awali uliofichwa.

Web Workers Hufuta Ushahidi

Tatizo hili linakuwa baya zaidi mara tu Web Workers wanapoingia katika usanifu. Wakosa kutokea ndani ya worker, kivinjari (browser) huondoa taarifa unazozihitaji zaidi.

Hivi ndivyo inavyotokea hasa. Ndani ya worker, kosa lisilodhibitiwa (uncaught exception) huchochea ErrorEvent. Ikiwa worker itapitisha kosa hilo kwenye thread kuu (main thread), mfumo wa kawaida ni kuchukua string ya message na kuituma kupitia mpaka huo. Thread kuu inapokea string hiyo, inatengeneza object mpya ya Error kutoka nayo, na kuirekodi au kuitoa tena. Kinachotokea kwenye DevTools ni kosa lililojengwa upya ndani ya mshughulikiaji wa ujumbe (message handler) wa thread kuu. Jina halisi la faili, namba ya mstari, na stack trace hutupwa. Mahali halisi pa hitilafu inakuwa haionekani.

Kwa hivyo, wakati pageWidth iliyokosekana ilipochochea ReferenceError ndani ya worker, thread kuu iliripoti maandishi tu "pageWidth is not defined" kwenye sehemu ambapo ujumbe ulishughulikiwa. Kazi halisi ya kiwango cha moduli ilikuwa imekaa katika