TROIA nedir, CANIAS ERP'nin neresinde duruyor?

TROIA, CANIAS ERP'nin uygulama sunucusunda çalışan, IAS (Industrial Application Software) tarafından geliştirilmiş 4. nesil (4GL) bir iş uygulaması programlama dilidir. C# ya da Java'dan gelip TROIA ile ilk karşılaşan biri için en şaşırtıcı şey, dilin "genel amaçlı" değil, tamamen ERP'nin kendi kavramları etrafında tasarlanmış olması: ekran (dialog), rapor, sınıf (class), bileşen (component) ve iş akışı (transaction) — CANIAS'ın gördüğünüz her parçası bu beş yapı taşından biriyle yazılır. NuGet paketi kurup bir kütüphaneyi projeye eklemek gibi bir şey yok; her şey uygulama sunucusunun kendi içinde, kendi ekosisteminde yaşıyor.

Bu yazıda dilin temel yapı taşlarını, kodun nasıl derlendiğini, TROIA'ya özgü ve çoğu geliştiricinin ilk günlerde şaşırdığı "Hotline" (değişiklik talebi) mekanizmasını ve gerçek bir method örneğini birlikte gerçekten iş yapan bir gözle inceleyeceğiz.

Beş yapı taşı: Class, Dialog, Component, Report, Transaction

  • Class: Nesne yönelimli yapı — kalıtım ve encapsulation destekler. Ama dikkat: TROIA'da genelde iş nesnesinin alanları (field) class member olarak değil, TABLE tipinde tutulur. Yani "bir Sipariş nesnesi" C#'taki gibi property'lerle değil, tek satırlık bir tabloyla modellenir. İlk başta tuhaf gelir, ama CANIAS'ın veritabanı odaklı mimarisiyle tutarlı: her şey zaten satır/sütun olarak akıyor.
  • Dialog: Bir web formu ya da Swing penceresi gibi düşünün — textfield, buton, combobox, tablo, grafik gibi kontroller barındırır ve "form yüklendi", "butona tıklandı" gibi olaylara (event) tepki verir.
  • Component: Birden fazla basit kontrolü bir araya getiren, tekrar kullanılabilir özel bir UI parçası. TROIA'da fonksiyon/modül import etme kavramı olmadığı için, kod tekrarını önlemenin pratik yollarından biri budur — bir arama kutusu + sonuç listesi kombinasyonunu on farklı dialogda elle kopyalamak yerine bir component'e taşırsınız.
  • Report: Dialog'a benzer ama çıktısı PDF, düz metin ya da yazıcıya yönlendirilebilir; label, görsel, barkod gibi kontrollerle görsel tasarım aracı üzerinden hazırlanır.
  • Transaction: Adı, açıklaması ve başlangıç dialog'u olan bir veritabanı kaydı — bağımsız bir TROIA uygulamasını tanımlar. Bir transaction'ı çalıştırdığınızda sistem otomatik olarak onun başlangıç dialog'unu açar. CANIAS'taki her ekran kodu (SOME01, DEVT01 gibi) aslında bir transaction kaydına karşılık gelir.

Kod nasıl ikili hale geliyor: Convert/Save, .cls ve .dlg

TROIA yorumlanan değil, derlenen bir dildir — ama derleme adımı "Convert" ya da "Save" olarak adlandırılıyor. Yazdığınız kaynak kod parse edilip ikili forma çevriliyor; sınıflar .cls, dialog/rapor/component'ler .dlg uzantılı ikili dosyalar olarak saklanıyor. Kaynak kodda tek bir parse hatası varsa sistem ikili dosyayı hiç oluşturmuyor — yani "kısmen derlenmiş, çalışan ama eksik" bir ara durum yok, ya tamamen derlenir ya da eski binary olduğu gibi kalır. Pratikte bu, production ortamında yarım bırakılmış bir değişikliğin asla yayına çıkmayacağı anlamına geliyor; ama geliştirme sırasında da "neden değişikliğim görünmüyor" sorusunun en sık cevabı, bir yerde gözden kaçan bir parse hatasıdır.

Geliştirme ortamı olarak TROIA IDE kullanılır; okuma-yazma yetkisi DEVELOPMENT, salt okunur erişim DEVELOPMENT(READ-ONLY) iznine bağlıdır. Günlük işte en çok karşılaşacağınız üç transaction: DEVT01 (veritabanı tarayıcı — bir tabloya elle bakmak için), DEVT31 (Trace dosyaları — az sonra değineceğim) ve DEVT30 (Runcode — küçük bir kod parçasını tam bir class/dialog yazmadan hızlıca test etme transaction'ı; TROIA'da REPL karşılığı diyebiliriz).

Yorum satırı neden // ile çalışmıyor, ve diğer sözdizimi kuralları

TROIA'nın sözdizimi kuralları C ailesi dillerinden gelenler için birkaç yerde bilinçli farklılık gösterir:

  • Tek yorum biçimi /* ... */ blok yorumudur. // ya da # desteklenmez — bu yüzden bir satırı geçici olarak devre dışı bırakmak istediğinizde de blok sözdizimini kullanmanız gerekir.
  • Atama = operatörüyle yapılır; MOVE 'değer' TO DEĞİŞKEN; alternatif bir sözdizimidir ama sağ tarafında ifade (expression) kabul etmez, sadece sabit ya da başka bir değişken alabilir.
  • Değişkenler LOCAL:, GLOBAL:, MEMBER: ya da OBJECT: anahtar kelimeleriyle, önce tip sonra isim gelecek şekilde tanımlanır: LOCAL: INTEGER SAYI;. Bu dört kapsamın (scope) birbirinden farkını netleştirmemek, TROIA'da en sık görülen bug kaynaklarından biridir — özellikle MEMBER değişkeni LOCAL sanıp, bir instance'ın state'ini beklenmedik şekilde başka bir çağrıya sızdırmak.
  • Koşul bloğu IF ... THEN ... ELSE ... ENDIF; şeklindedir — ELSEIF yoktur, gerekirse ELSE bloğunun içine iç içe IF yazılır.
  • FOR/FOREACH döngüsü yoktur; sayaç tabanlı döngüler için WHILE, tablo satırları için LOOP AT, string'ler için PARSE kullanılır.
  • Bir class method'unun girdi parametreleri, gövdenin en başında PARAMETERS: bloğunda tanımlanır — C#'taki gibi method imzasının parçası değil, gövdenin içinde ayrı bir bildirim.

Hotline: TROIA'da her değişikliğin bir gerekçesi olmak zorunda

Diğer birçok dilde göremeyeceğiniz, ama kurumsal ERP dünyasında hayati bir mekanizma: Hotline, TROIA'nın "değişiklik talebi" (change request) kavramı. Kural nettir — bir TROIA öğesini (class, dialog, transaction, ne olursa olsun) bağlı bir hotline kaydı olmadan değiştiremezsiniz. İlk bakışta bürokratik bir engel gibi görünür, ama gerçek bir CANIAS projesinde bu, "bu değişikliği neden yaptık, kim istedi, hangi tarihte" sorusuna her zaman bir cevabınızın olması demek. Denetim (audit) ve versiyon geçmişi dilin kendisine gömülü — ayrı bir change-management aracı kurmanıza gerek kalmıyor. .NET tarafından gelen biri için en yakın benzetme, her commit'in zorunlu olarak bir Jira ticket numarasına bağlanması, ama bunun IDE seviyesinde zorunlu kılınmasıdır.

Gerçek bir method örneği

Bir tutara, oranı %50'yi geçemeyecek şekilde iskonto uygulayan bir class method:

/* Belirtilen tutara iskonto oranını uygular. Oran %50'yi geçemez. */
PARAMETERS:
    DECIMAL TUTAR,
    DECIMAL ORAN;

LOCAL:
    DECIMAL SONUC;

IF ORAN > 50 THEN
    RETURN TUTAR;
ENDIF;

SONUC = TUTAR - (TUTAR * ORAN / 100);
RETURN SONUC;

Bu gövde bir class method'una ait. Aynı class içinden THIS.HesaplaIskonto(1000, 20), bir instance üzerinden ise INSTANCE.HesaplaIskonto(1000, 20) şeklinde çağırırsınız.

Sık yapılan hatalar:

  • ORAN > 50 yerine ORAN => 50 yazıp derleme hatası almak — TROIA'da "büyük eşit" >= şeklinde yazılır, => değil.
  • IF bloğunun sonunda ENDIF; unutmak — parse hatası olarak geri döner ama hata mesajı genelde bloğun kendisini değil, ondan çok sonraki bir satırı işaret eder; bu yüzden "neden orada hata versin ki" diye düşünmeden önce yukarıdaki tüm IF/ENDIF eşleşmelerini saymak iyi bir alışkanlıktır.
  • DECIMAL yerine INTEGER kullanıp yüzde hesaplarında küsuratı kaybetmek.
  • LOCAL: bloğunda tanımlanmayan bir değişkeni kullanmaya çalışmak — TROIA örtük (implicit) değişken tanımlamaya izin vermez, her değişken kapsamıyla birlikte açıkça bildirilmelidir.

Sıradaki yazı

Bir sonraki yazıda TROIA'nın en çok kullanılan veri tipi olan TABLE'ı, satırlar üzerinde gezinmek için LOOP AT'in gerçek performans farkı yaratan varyasyonlarını (özellikle hash index ile LOCATE'in normal döngüye göre büyük tablolarda ne kazandırdığını) ele alacağım.