Setiap pemuatan PDF ranap dengan ReferenceError yang sama. Jejak timbunan (stack trace) tidak menunjukkan apa-apa yang berguna, dan spesifikasi yang memandu kod tersebut kelihatan sangat munasabah di atas kertas. Ia menyatakan untuk menyemak bendera ciri (feature flag), dan untuk membandingkan setiap wilayah dengan separuh daripada pageWidth. Ia menerangkan apa yang sepatutnya berlaku. Namun, ia gagal menerangkan dari mana pageWidth sepatutnya diperoleh, dan satu ketinggalan itu sudah cukup untuk melumpuhkan keseluruhan saluran paip (pipeline).

Dokumen seni bina bagus dalam menerangkan tingkah laku. Ia sering kali sangat teruk dalam menerangkan sempadan. Ayat yang menyatakan "fungsi menyemak x terhadap pageWidth" bukanlah satu kontrak teknikal; ia adalah satu naratif yang menyembunyikan kebergantungan di dalam bahasa Inggeris biasa. Apabila seorang pembangun membaca ayat tersebut dan menulis fungsi peringkat modul yang merujuk kepada pageWidth melalui namanya, kod tersebut kelihatan betul kerana ia memenuhi huraian tersebut. Kemudian, masa larian (runtime) cuba menyelesaikan nama tersebut, tidak menemui apa-apa dalam skop, dan mencetuskan ralat (throws).

Refaktor yang Sepatutnya Mudah

Saya melihat corak yang sama ini semasa refaktor pemasangan halaman. Spesifikasi tersebut menyenaraikan dua keperluan yang kelihatan bersih:

  • Semak FEATURE_LAYOUT.
  • Bandingkan setiap wilayah dengan pageWidth / 2.

Pembangun tersebut mengikut arahan dengan setia. Mereka mengekstrak fungsi utiliti pada skop modul dan memasukkan pageWidth secara terus ke dalam badan fungsi tanpa menetapkannya sebagai parameter. Spesifikasi tersebut tidak menyatakan bahawa pageWidth mesti diterima melalui senarai argumen. Ia juga tidak menyatakan bahawa fungsi tersebut berada pada skop modul, di mana pageWidth tidak lagi kelihatan. Ia sekadar mengandaikan bahawa pelaksana memahami konteks pelaksanaan secara tersirat.

Hasilnya ialah ReferenceError pada setiap pemuatan PDF. Oleh kerana pemboleh ubah tersebut tiada dalam skop modul, fungsi tersebut terus mencetuskan ralat. Jika spesifikasi tersebut menamakan sempadan fungsi dan inputnya secara eksplisit, pembangun akan memasukkan pageWidth, dan pepijat tersebut mustahil berlaku dari segi struktur. Sebaliknya, arahan tersebut bertindak seperti perangkap, mengundang pelaksana untuk mencapai skop induk yang tidak wujud.

Empat Maksud "Uses"

Isu yang lebih mendalam ialah prosa tidak mempunyai sistem jenis (type system). Apabila spesifikasi menyatakan "fungsi menggunakan X," ayat tersebut adalah kabur dalam sekurang-kurangnya empat cara khusus dalam kod sumber JavaScript moden:

  • Fungsi menerima X sebagai parameter formal.
  • Fungsi membaca X daripada pemboleh ubah peringkat modul yang diisytiharkan dalam fail yang sama.
  • Fungsi melakukan closure ke atas X daripada skop induk yang bersarang.
  • Fungsi mengekstrak X daripada objek yang lebih besar yang dihantar kepadanya.

Setiap satu daripada pilihan ini memenuhi laras bahasa spesifikasi tersebut. Setiap satunya lulus analisis statik. Tetapi hanya satu sahaja yang betul untuk sempadan tertentu, dan pilihan yang salah membocorkan andaian merentasi sempadan tersebut dengan cara yang dikompil secara senyap.

Pembangun biasanya memilih jalan yang paling mudah semasa menulis. Jika pageWidth kebetulan berada dalam skop luar, mereka akan membacanya dari sana daripada mengubah tandatangan (signature) fungsi. Closure menyembunyikan kebergantungan tersebut. Kod berfungsi pada larian pertama, lulus ujian, dan dihantar (shipped). Berminggu-minggu kemudian, seseorang memindahkan fungsi yang sama ke fail berbeza untuk kegunaan semula, atau untuk meningkatkan kebolehbacaan. Skop induk hilang. Kod rosak, dan kerosakan tersebut kelihatan seperti regresi baharu walaupun punca utamanya adalah kebergantungan tersembunyi yang asal.

Web Worker Memadamkan Bukti

Masalah ini menjadi benar-benar buruk sebaik sahaja Web Worker memasuki seni bina tersebut. Apabila ralat berlaku di dalam worker, pelayar akan membuang maklumat yang paling anda perlukan.

Inilah yang sebenarnya berlaku. Di dalam worker, pengecualian yang tidak ditangkap (uncaught exception) mencetuskan ErrorEvent. Jika worker tersebut menghantar ralat itu ke bebenang utama (main thread), corak tipikal adalah untuk mengambil rentetan (string) message dan menghantarnya merentasi sempadan. Bebenang utama menerima rentetan tersebut, membina objek Error baharu daripadanya, dan mencatat atau mencetuskan semula ralat tersebut. Apa yang muncul dalam DevTools ialah ralat yang dibina semula di dalam pengendali mesej bebenang utama. Nama fail asal, nombor baris, dan jejak timbunan (stack trace) dibuang. Lokasi sebenar kegagalan tersebut menjadi tidak kelihatan.

Jadi apabila pageWidth yang hilang mencetuskan ReferenceError di dalam worker, bebenang utama hanya melaporkan teks "pageWidth is not defined" pada lokasi di mana mesej tersebut dikendalikan. Fungsi peringkat modul yang sebenar berada di