İçeriğe geç
academia.sh

Ders 01 / 14

Modül İhtiyacı

Genel kapsamın paylaşılmasından doğan ad çakışması sorunu, kapanışla kurulan ad alanı kalıbı ve modül kapsamının bu soruna verdiği yanıt.

İçindekiler

Eşzamansız JavaScript ve Çalışma Zamanı kursu, tek bir programın nasıl yürüdüğünü ve sorunlarının nasıl tanılandığını ele aldı. Bu kurs bir üst düzeye çıkar: bir program tek dosyada durmadığında, parçaların birbirini nasıl bulduğu ve kimin neyi görebildiği sorusuna.

Soru masum görünür. Kodu iki dosyaya bölmek, dosyaları birbiri ardına yüklemekten ibaret sanılır. Bu varsayımın nerede kırıldığını görmek, modül kavramının neden dile eklendiğini de açıklar.

Genel Kapsam Ortak Bir Kaynaktır

Dilin ilk yürütme birimi betik (script) idi: kaynak metin ayrıştırılır ve en üst düzeydeki bildirimleri genel kapsama yazılır. İki ayrı dosya, aynı çalışma zamanında art arda yüklendiğinde ayrı bir dünya kurmaz; ikisi de aynı genel nesneye yazar.

Bu davranış, çalışma zamanının betik değerlendirme arayüzüyle doğrudan gözlenebilir:

// dosya: betikler.mjs
import vm from 'node:vm';

// İki ayrı "betik", aynı genel kapsamda çalıştırılıyor.
vm.runInThisContext('var surum = "cizim-1.0"; function bicimle(d) { return "cizim: " + d; }');
vm.runInThisContext('var surum = "rapor-2.0"; function bicimle(d) { return "rapor: " + d; }');

console.log(globalThis.surum);
console.log(globalThis.bicimle('x'));
$ node betikler.mjs
rapor-2.0
rapor: x

İlk betiğin surum değişkeni ve bicimle fonksiyonu kayboldu. Hata yok, uyarı yok: ikinci bildirim birincinin üzerine yazdı. Program çalışmaya devam eder ve yanlış sonuç üretir.

Sorunun büyüklüğü dosya sayısıyla değil, ad sayısıyla artar. Yirmi dosyanın her biri beş genel ad tanımlıyorsa yüz adlık ortak bir havuz oluşur ve bu havuzun çakışmasız kalması yalnızca yazarların dikkatine bağlıdır. Programlama Temelleri kursunda kurulan kapsam kavramı burada işletim düzeyinde bir soruna dönüşür: paylaşılan yazılabilir bir ad alanı, yalıtımsız bir kaynaktır.

İkinci sorun sıralamadır. Bir dosya diğerinin tanımladığı fonksiyonu kullanıyorsa yükleme sırası önemlidir, ama bu sıra kodun kendisinde yazılı değildir; onu yükleyen tarafın bilmesi gerekir. Bağımlılık bilgisi kaynakta değil, kaynağın dışında durur.

Kapanışla Kurulan Ad Alanı

Dilde modül sözdizimi yokken bu iki soruna verilen yanıt, kapanışların (closure) kapsam yalıtımı özelliğini kullanmaktı. Bir fonksiyon tanımlanıp hemen çağrılır, iç adlar o fonksiyonun kapsamında kalır, dışarıya yalnızca seçilmiş bir nesne verilir:

// dosya: iife.mjs
var Olcer = (function () {
  var ayirac = /\s+/;
  function sozcukler(metin) { return metin.trim().split(ayirac); }
  return { sozcukler: sozcukler };
})();

console.log(Olcer.sozcukler('bir iki  uc'));
console.log(typeof ayirac);
$ node iife.mjs
[ 'bir', 'iki', 'uc' ]
undefined

ayirac dışarıdan görünmez; yalnızca Olcer adı genel kapsama yazılır. Bu kalıp, JavaScript’te Nesneler ve Fonksiyonlar kursundaki hemen çağrılan fonksiyon kalıbının doğrudan uygulamasıdır ve gerçek bir kazanç sağlar: yüz genel ad yerine bir genel ad.

Kazandırdığı kadarını da kaybettirir. Ad alanı nesnesinin kendisi hâlâ genel kapsamdadır ve çakışabilir. Bağımlılıklar yine sözleşmeyle taşınır: Olcer, kendisinden önce bir başka ad alanının yüklenmiş olmasını bekliyorsa bunu yazılı olarak bildiremez. Dahası hangi adın nereden geldiği, kaynağa bakılarak değil, tüm dosyalar taranarak bulunur.

Modül Kapsamı

Modül, bu sorunları dilin ayrıştırma düzeyinde çözer. Bir kaynak metin modül (module) olarak ayrıştırıldığında en üst düzeydeki bildirimleri genel kapsama değil, o modüle özgü bir kapsama yazılır:

// dosya: kapsam.mjs
const surum = 'metin-olcer-1.0';
function bicimle(d) { return 'olcer: ' + d; }

console.log(typeof globalThis.surum);   // modül kapsamındaki ad genel nesnede yok
console.log(typeof globalThis.bicimle);
console.log(this);                      // modülün en üst düzeyinde this
$ node kapsam.mjs
undefined
undefined
undefined

Üç çıktı da aynı şeyi söyler: modülün en üst düzeyi, genel kapsam değildir. surum ve bicimle adları dışarıdan görünmez; en üst düzeydeki this genel nesneye değil, tanımsıza bağlanır.

Ad alanı kalıbıyla arasındaki fark, yalıtımın kaynağıdır. Kapanış kalıbında yalıtım programcının kurduğu bir yapıydı ve bozulabilirdi; modülde yalıtım ayrıştırıcının kararıdır ve bozulamaz.

Betik ile Modülün Ayrımı

Aynı kaynak metin, iki farklı kural kümesiyle ayrıştırılabilir. Ayrım yalnızca kapsamla sınırlı değildir:

Ölçüt Betik Modül
En üst düzey bildirimler Genel kapsama yazılır Modül kapsamında kalır
En üst düzeydeki this Genel nesne Tanımsız
Katı kip Açıkça istenmelidir Her zaman etkindir
İçe/dışa aktarım sözdizimi Geçersiz Geçerli
Değerlendirme zamanı Karşılaşıldığı anda Bağımlılıklar çözüldükten sonra

Son satır belirleyicidir ve sonraki derste ayrıntılandırılacaktır. Bir metnin hangi kurala göre ayrıştırılacağı, çalışma zamanının okuduğu bir işaretle belirlenir: dosya uzantısı, bildirim dosyasındaki tip alanı ya da betiği yükleyen tarafın verdiği tür bilgisi. Aynı karakter dizisi, bu işarete göre farklı anlam kazanır.

Bir Modül Sisteminden Beklenenler

Bir modül sisteminin çözmesi gereken sorunlar, yukarıdaki iki denemeden çıkarılabilir:

Yalıtım. Modülün iç adları dışarıdan görünmemeli; dışarıya ne verdiğini modülün kendisi seçmeli.

Açık arayüz. Bir modülün neye bağlı olduğu ve ne sunduğu, kaynağın kendisinde yazılı olmalı. Bağımlılık bilgisi yükleyicinin belleğinde değil, kodda durmalı.

Belirlenimci çözümleme. Bir bağımlılık adı, her ortamda aynı dosyaya karşılık gelmeli. Ad çözümleme kuralı yazılı ve öngörülebilir olmalı.

Tek değerlendirme. Aynı modül birden çok yerden istendiğinde bir kez değerlendirilmeli; tuttuğu durum tüm kullanıcılar için aynı olmalı. Bu güvence, Bilgisayarlar Nasıl Çalışır kursundaki bağlama ve yükleme dersinde tanıtılan simge çözümlemesinin dil düzeyindeki karşılığıdır: bir ad, tek bir tanıma bağlanır.

Bu dört güvence, kurs boyunca incelenecek her mekanizmanın ölçütüdür. Paket çözümlemesi üçüncüsünün, önbellekleme dördüncüsünün gerçekleştirimidir.

Kursun Örneği

Kurs boyunca tek bir somut örnek geliştirilecektir: metin-olcer adlı küçük bir kütüphane paketi. İşi, verilen metnin sözcük ve cümle sayısını, ortalama sözcük uzunluğunu ve en uzun sözcüğünü hesaplamaktır. Tek dosyalık bir betik olarak başlar; modüllere bölünür, dışa aktarım yüzeyi tanımlanır, bir bağımlılık ekler, sürüm işaretlenir, bir çalışma alanının parçası olur ve sonunda derleme ile çözümleme araçlarına bağlanır.

İlk hâli tek dosyadır ve hiçbir şey dışa aktarmaz:

// dosya: olcer.mjs
const SOZCUK_AYIRACI = /[^\p{L}\p{N}]+/u;
const CUMLE_AYIRACI = /[.?]+/;

function sozcuklereBol(metin) {
  return metin.split(SOZCUK_AYIRACI).filter((s) => s.length > 0);
}

function cumlelereBol(metin) {
  return metin.split(CUMLE_AYIRACI).map((c) => c.trim()).filter((c) => c.length > 0);
}

function ortalamaUzunluk(sozcukler) {
  if (sozcukler.length === 0) return 0;
  return sozcukler.reduce((t, s) => t + s.length, 0) / sozcukler.length;
}

function olc(metin) {
  const sozcukler = sozcuklereBol(metin);
  return {
    sozcukSayisi: sozcukler.length,
    cumleSayisi: cumlelereBol(metin).length,
    ortalamaUzunluk: ortalamaUzunluk(sozcukler),
  };
}

console.log(olc('Modul kendi kapsamini tasir. Betik genel kapsamda calisir.'));
$ node olcer.mjs
{ sozcukSayisi: 8, cumleSayisi: 2, ortalamaUzunluk: 6.125 }

Bu dosyada üç ayrı sorumluluk iç içedir: metni parçalara ayırma, sayısal ölçüm hesaplama ve sonucu sunma. Üçü de aynı kapsamı paylaştığı için birbirinin adlarını doğrudan görür. Dosya büyüdükçe bu görünürlük bir tasarım kararı olmaktan çıkıp bir kaza kaynağına dönüşür: SOZCUK_AYIRACI sabiti yalnızca bölme işlevlerini ilgilendirdiği hâlde tüm dosyaya açıktır.

Bir sonraki derste bu dosya sorumluluklarına göre bölünecek ve bölünmenin dışa aktarım yüzeyine etkisi incelenecektir. Bölme işleminin asıl sorusu, hangi kodun hangi dosyaya gideceği değil, hangi adın dışarıdan görülebilir kalacağıdır.

Özet

  • Betik olarak ayrıştırılan kaynağın en üst düzey bildirimleri genel kapsama yazılır; iki dosya aynı adı tanımladığında ikincisi birincisini sessizce geçersizleştirir.
  • Kapanışla kurulan ad alanı kalıbı genel ad sayısını azaltır, ancak yalıtımı programcının disiplinine bırakır ve bağımlılıkları kodda bildirmez.
  • Modül olarak ayrıştırılan kaynağın en üst düzey bildirimleri modül kapsamında kalır; katı kip her zaman etkindir ve en üst düzeydeki this tanımsızdır.
  • Bir modül sistemi dört güvence verir: yalıtım, açık arayüz, belirlenimci çözümleme ve tek değerlendirme.

Sonraki Adım

Modül kapsamı, adların dışarı sızmasını engeller; ama bir modülün başka bir modüle bilerek ne verdiğini henüz tanımlamadık. Sonraki ders, dilin standart modül sözdizimini ele alır: dışa aktarım biçimleri, içe aktarımın bağ kurma davranışı ve bu sözdiziminin statik yapısının neden yalnızca bir yazım tercihi olmadığı.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat