Bu yazı dizisinin en çok atlanan, ama en pahalıya patlayan konusu

troiaya-giris yazımda LOCAL/GLOBAL/MEMBER/OBJECT kapsamlarını birbirinden ayırt etmemenin "TROIA'da en sık görülen bug kaynaklarından biri" olduğunu söylemiştim. Bu yazıda o iddiayı gerçek bir zemine oturtalım: TROIA'nın üç seviyeli kapsam hiyerarşisini ve dördüncü, bağlama göre şekil değiştiren OBJECT anahtar kelimesini tam olarak anlamak.

Üç seviyeli hiyerarşi

  • Global kapsam: Transaction/uygulama boyunca, herhangi bir dialog ya da class metod/olayından erişilebilir. troiada-transaction-cagirmak yazısında bahsettiğim gibi, bir transaction'a geçilen girdi parametreleri de bu kapsamda global değişkenlere dönüşüyor.
  • Member kapsamı: Sadece o class'ı tanımlayan tüm metodlar içinde erişilebilir; @ operatörüyle erişilir (INSTANCE1@XPOS). Kural net: bir member değişken, aynı adı taşıyan daha geniş kapsamlı (global) bir değişkeni gölgeler (override eder).
  • Local kapsam: Sadece tanımlandığı metod/olay içinde geçerli. Aynı şekilde: bir local değişken, aynı adı taşıyan daha geniş kapsamlı (member, global) bir değişkeni gölgeler. TROIA'da blok seviyesinde kapsam (IF bloğu içinde tanımlanan bir değişkenin sadece o blokta geçerli olması gibi) desteklenmiyor — bir metodun neresinde tanımlarsanız tanımlayın, o değişken metodun tamamında geçerli.

Tanımlama komutları

GLOBAL:
    STRING METINDEGISKENI1,
    TABLE TABLODEGISKENI1,
    INTEGER SAYIDEGISKENI;

MEMBER:
    STRING ADI,
    INTEGER XKONUMU;

LOCAL:
    STRING METINDEGISKENI1,
    TABLE TABLODEGISKENI1;

Üçü de aynı sözdizimini paylaşıyor, farklı olan sadece anahtar kelime ve geçerlilik alanı.

OBJECT: bağlama göre dört farklı anlam

OBJECT anahtar kelimesi, hangi bağlamda ve hangi veri tipiyle kullanıldığına göre farklı bir kapsama karşılık geliyor — belgelerin tablosu şöyle:

Veri Tipi Dialog/Rapor Class Constructor Class Metodları
Table / Class instance Global Global Global
Basit tipler (STRING, INTEGER, vb.) Global Member Local

Yani aynı OBJECT: bloğunda bir STRING tanımlarsanız, bunun gerçek kapsamı nerede yazdığınıza bağlı olarak Global, Member ya da Local olabilir. Belgelerin kendi tavsiyesi de tam olarak bu karmaşıklığa işaret ediyor: "okunabilirliği artırmak için OBJECT yerine GLOBAL, LOCAL ve MEMBER kullanmak önerilir." troiada-siniflar yazısındaki instance tanımlama örneğinde (OBJECT: MATEMATIKYARDIMCISI HESAPLAYICI;) OBJECT kullanmıştım — orası bir class instance'ı olduğu için tablo yukarıdaki gibi zaten "Global" diyor, yani belirsizlik yaratmıyor. Ama bir STRING/INTEGER için OBJECT kullanırsanız, kodu okuyan biri (ileride siz dahil) hangi kapsamda olduğunu tablo olmadan kestiremez.

Sistem değişkenleri: kendi adlarınızla çakışamaz

SYS_CURRENTDATE, SYS_USER, SYS_CURRENTDIALOG, CONFIRM gibi önceden tanımlı global değişkenler var. Kural kesin: sistem değişkeniyle aynı isimde bir değişken tanımlayamazsınız. Yeni bir değişken adı seçerken SYS_ önekiyle başlayan ya da CONFIRM gibi bilinen bir sistem adıyla çakışıp çakışmadığını kontrol etmek, "neden bu değişkeni tanımlayamıyorum" sorusuna baştan cevap verir.

Tanımsız değişkenler ve tip sorgulama

Tanımlanmamış bir değişkeni kullanırsanız, TROIA onun adını bir string gibi geri döndürür — ama bu gerçek bir STRING tipi değildir. GETVARTYPE() fonksiyonu bir değişkenin tipini string olarak döner; tanımsız bir değişken için 'UNKNOWN TYPE' sonucunu verir. Bir değişkenin gerçekten beklediğiniz tipte tanımlı olduğundan emin olmanız gereken genel/dinamik kod yazarken bu fonksiyon işinize yarar.

Yeniden tanımlama davranışı

Aynı değişkeni bir döngü içinde tekrar tekrar "tanımlıyor" gibi görünen kod yazarsanız (örneğin bir LOCAL: bloğunu mantıksal olarak tekrar çalıştırırsanız), sonraki tanımlama komutları önceki tanımı görmezden gelmiyor, değişkeni varsayılan değerine sıfırlıyor. Bu, troiada-siniflar yazısındaki _VARIABLES/_CONSTRUCTOR'ın "sadece bir kez çalışır" kuralından farklı bir davranış — kafanızda ikisini birbirine karıştırmayın.

Gerçek bir örnek: gölgeleme (shadowing) tuzağı

GLOBAL:
    STRING DURUM;

/* ... DURUM transaction genelinde 'AKTIF' olarak ayarlanmış olsun ... */

/* Bir metodun içinde: */
LOCAL:
    STRING DURUM;

DURUM = 'BEKLEMEDE';
/* Bu satırdan sonra bu metod içindeki DURUM 'BEKLEMEDE',
   ama global DURUM hâlâ 'AKTIF' - metod bittiğinde global etkilenmemiş olur. */

Bu davranışın kendisi doğru ve tutarlı — asıl risk, bunu bilmeden yazıp "neden global durumu güncellemedim" diye şaşırmak. Aynı isimde bir local değişken tanımladığınızda, o metod içindeki her referans local olana gider; global olanı gerçekten güncellemek istiyorsanız farklı bir isim kullanmanız (ya da local tanımı hiç yapmamanız) gerekir.

Sık yapılan hatalar

  • MEMBER'ı LOCAL sanıp bir instance'ın state'ini istemeden sızdırmak — troiaya-giris yazısında bahsettiğim klasik hata: bir class metodunda MEMBER: ile tanımlanan bir değişkenin, o metod bitince sıfırlanacağını düşünmek; oysa member değişken instance'ın tüm ömrü boyunca yaşar.
  • OBJECT'i her yerde aynı kapsamı verecekmiş gibi kullanmak — yukarıdaki tabloyu bilmeden OBJECT ile basit tip tanımlamak, dialog kodunda global, class constructor'da member, class metodunda local gibi üç farklı davranışla karşılaşmanıza yol açar.
  • Global bir değişkeni gölgeleyen bir local/member tanımı yapıp global olanı güncellediğini sanmak — yukarıdaki örnekteki gibi, isim çakışmaları sessizce yanlış değişkeni günceller.
  • Sistem değişkeniyle çakışan bir isim seçmeye çalışmak — SYS_ önekli ya da CONFIRM gibi bilinen adları kullanmayı denemek, tanım hatasına yol açar; yeni değişken adlarını seçerken bu çakışmayı önceden kontrol edin.
  • Her şeyi GLOBAL tanımlayıp kapsamı hiç düşünmemek — belgelerin de belirttiği gibi bu iyi bir pratik değil; bir değişkeni ihtiyaç duyduğu en dar kapsamda tanımlamak, hem gölgeleme risklerini azaltır hem de kodu okuyanın "bu değişken nereden geliyor" sorusunu daha hızlı cevaplamasını sağlar.

Kapanış

Bu yazıyla, troiaya-giris yazısında başladığım TROIA temel dizisini büyük ölçüde tamamlamış olduk — dilin sözdiziminden veri erişimine, entegrasyonlardan platform mimarisine kadar. Yeni sorular ya da spesifik senaryolar için yorum bırakabilirsiniz.