Friday, June 28, 2013

Modelleme

Yazılım bir dizi etkinliğin gerçekleştirildiği bir sürecin sonucu olarak ortaya çıkar. Basitçe bu etkinlikleri, müşteriden isteklerin alınması, isteklerin çözümlenmesi, sistemin çözümlenmesi, mimari tasarım, yazılımın tasarımı, kodlama ya da gerçekleme, test etme, kurulum ve teslim etme ve bakım olarak sıralayabiliriz. İlk etkinlik müşteriden isteklerin alınmasıdır. Müşteri çoğu zaman tam olarak neyi istediğini bilmez ya da bilemez. Gerçeklemesini istediği sistemle ilgili olarak kafasında genel bir fikir vardır. Bu bir eksiklik olarak görülmemelidir. Yazılım geliştirme sürecinin doğal bir özelliğidir. İsteklerin alınması aşamasının temel amacı da müşteriye aklındaki sistemi tanımlamasına yardımcı olmaktır. İsteklerin çözümlenmesi aşamasında yazılımın gereksinimleri tanımlanır. Yazılımla ilgili olarak basitçe iki tür gereksinimden söz edebiliriz: İşlevsel gereksinimler ve işlevsel olmayan gereksinimler. İşlevsel gereksinimler, yazılımın, kullanan için bir amacı yerine getiren, ona fayda sağlayan yeteneklerini tanımlar. Örneğin, para çekme makinası için para çekme, para yatırma, bakiye sorma, fatura ödeme, havale yapma birer işlevsel gereksinimdir. İşlevsel olmayan gereksinimler ise genellikle yazılımın hizmet kalitesine yönelik tanımlamalar içerir. Örneğin, para çekme makinası örneği için banka müşterisinin banka kartını sisteme tanıttıktan sonra para çekme seçimini yapıp, tutarın onaylamasının ardından, sistemin en geç 10 saniye içinde paragözüne seçilen tutar kadar banknotu bırakması isteniyorsa, bu bir işlevsel olmayan bir gereksinimdir. Gereksinimler dışında iş kuralları ve kısıtlar da bu aşamada not edilir. Örneğin, banka müşterisinin para çekme makinasından günlük olarak en fazla 2,500 TL para çekebileceği tanımlanmış ise bu bir iş kuralı olarak not edilir. Örneğin, yine para çekme makinasının en fazla 2GB bellekli ve Linux işletim sistemli bir platform üzerinde çalışabilmesi istenmişse, bu bir kısıt olarak not edilir. Sistem çözümleme aşamasında sistemin nasıl çalıştığı anlaşılır, tasarım aşamasında ise problem çözülmeye çalışılır. Gerçekleme aşamasında ise çözüm, seçilen bir programlama dilinde kodlanır. Kodlanan çözüm test edilerek sınanır ve müşteriye teslim edilir.
Bu etkinlikleri bir sürecin parçası haline getirmek ve yönetebilmek gerekir. Bu söylemesi kolay ancak gerçeklemesi güç bir söylemdir. Güçlük büyük ölçüde etkinlikleri bir disiplin haline getirebilmekte yatıyor. Profesyonel ile amatörü biri birinden ayıran özelliklerden biri de profesyonelin elindeki işi bir düzen içinde yapmasıdır. Örneğin tıp doktorluğu, inşaat mühendisliği, makina mühendisliği profesyonellik gerektiren mesleklerdir. Yazılım mühendisliğinin de böyle olması beklenir. Yazılım mühendisliğinde süreç yönetim modellerinin amaçlarından biri de bu disiplinleri tanımlamak ve geliştirme ekibindeki her bir rol için ne zaman neyi hangi sırada yapacağını tanımlamaktır. Son yıllarda çevik süreçler olarak adlandırılan öz yinelemeli, artımsal, müşteri odaklı süreç yönetim modelleri öne çıkmıştır. Scrum ve eXtreme Programming (XP) çevik süreç yönetim modellerine örnek olarak verilebilir. Scrum'da belirli bir disiplin yoktur. Bunu daha çok çalışanların deneyimine bırakmaktadır. Bu aynı zamanda Scrum'a getirilen en önemli eleştirilerden biridir. XP modelinde ise bu pratikler tanımlanmıştır. İkili programlama, test güdümlü programlama bu pratiklerden bazılarıdır. 2-4 haftalık dönemlerle yazılım yaşam döngüsü tekrarlanır ve her yineleme sonunda çalışan uygulama müşteri ile paylaşılır ve geri besleme alınır. Her yineleme sonunda uygulama yeni bir işlev kazanır ve bir önceki yinelemenin hataları düzeltilir. Yinelemeler ilerledikçe hem ekip hem de müşteri gerçeklenen yazılım hakkında daha fazla deneyime ve isterler konusunda daha net bir görüşe sahip olacaktır. Bu ise bir taraftan ekibin özgüvenini arttırırken diğer taraftan müşterinin projenin başarıyla tamamlanacağına olan inancını arttıracaktır.
Yazılım geliştirmek zor iştir. Kaliteli yazılım geliştirmek ise daha da zordur. Yazılım projelerin önemli bir kısmı başarısızlıkla sonuçlanmaktadır. Başlangıçta belirlenen bütçe, insan kaynağı ve süre ile ilgili hedeflerin çoğu tutturulamamaktadır. Burada temel güçlük yazılım sistemlerinin karmaşıklığı yanında yazılımın soyut bir kavram olmasından kaynaklanmaktadır. Bilişim sistemleri yazılım ve donanım sistemlerinden oluşur. Donanım para verip satın aldığımızda dokunabileceğimiz, beş duyu organımızla hissedebileceğimiz bir varlıktır. Yazılım ise çalışabilmek için donanıma ve diğer yazılımlara ihtiyaç duyar. Yazılımın ürettiği sonucu ancak bir donanım aracılığı ile algılayabiliriz. Para çekme makinası örneğinde, kartımızı tanıtıp para çekme işleminin adımlarını başarı ile tamamladığımızda, parayı paragözünden alırız ve o an yazılımın para çekme işlevine dokunmuş oluruz. Soyut düşünme konusunda çok başarılı olduğumuz söylenemez. Bu yüzden yazılımı görselleştirmek üzere çeşitli şekiller çizmeyi tercih ediyoruz. Unified Modeling Language (UML), Business Process Modeling Notation (BPMN), System Modeling Language (SysML) gibi standart varlığı ortak bir gösterim dili oluşturması açısından önemlidir. Yazılım yaşam döngüsünde tüm etkinliklerin bir çıktısının olması istenir. Bu çıktı bazen yazılan bir kod bazen çizilen bir UML diyagramı bazen de paydaşlara gönderilen bir e-posta olabilir. Çevik süreç yönetim modellerinde ise UML diyagramı çizilmesine soğuk bakılır. Gerçekten karmaşık bir durumu anlamayı ya da açıklamayı kolaylaştıracağı düşünülüyorsa UML diyagramı çizilebilir. Çevik süreçlerde en iyi modelin çalışan kod olduğu düşünülür.
UML modelin nasıl çizileceğini tanımlar, ancak bir problem verildiğinde problemin bilgisayardaki modelinin nasıl üretileceğini söylemez, yol göstermez. Bir kurumda iki tür sistemden söz etmek mümkündür: Bilişim Teknolojileri Sistemi (BTS) ve İş Süreçleri Sistemi (İSS). İş süreçleri firmaya para kazandıran faaliyetler olarak düşünüle bilinir. BTS'nin amacı İSS'den aldığı verileri işleyip bilgiye dönüştürmek ve karar verme sürecinde kullanılmak üzere İSS'ye aktarmaktır. Genellikle BTS'yi modellemek için UML, İSS'yi modellemek için BPMN kullanılmaktadır. İstenirse UML standardındaki etkinlik (=Activity) diyagramını kullanarak da iş süreçleri modellenebilir. UML bir programlama dili değildir. Ancak istenirse bilgisayar destekli yazılım mühendisliği araçları kullanılarak iskelet kod üretebilinir. UML çalıştırıla bilinir bir model değildir. BPMN 2 ise çalıştırılabilir bir model sunar. Oracla SOA and BPM Suite 11g ürünü kullanılarak web tabanlı bir arayüz üzerinden BPMN'de modelleme yapmak mümkündür. Oracle SOA Suite üzerinde yer alan BPMN motoru bu modeli çalıştırır.
UML profil tanımlanarak genişletilebilir bir modelleme dilidir. Bu profillerden en bilineni SysML profilidir. UML temel olarak yazılım sistemlerinin modellenmesi için tasarlanmıştır. SysML UML'i her türlü sisteme uygulanabilmesini sağlar. SysML UML'de yer alan 14 diyagramın 7'sini kullanır. SysML’de bu diyagramlara ek olarak yeni iki diyagram tanımlanmıştır: ister diyagramı ve parametrik diyagramı.
Hem UML hem de SysML Object Management Group (OMG) tarafından yönetilen standartlardır. OMG kar amacı gütmeyen bir organizasyondur ve yayınladığı standartlar ticari kullanıma açıktır. UML’in güncel sürümü 2.4.2 ve SysML’in güncel sürümü 1.3’dür. UML 2'de diyagramlar, elemanlar ve ilişkiler yine UML elemanları ve sınıf diyagramı kullanılarak tanımlanmıştır. Bu yapı Meta-Model Facility (MOF) olarak adlandırılmaktadır. Profiller de yine MOF kullanılarak tanımlanır. Örneğin SysML profilinin MOF ile tanımlanmış bir meta modeli vardır.
Problemi anlamak, çözmek, çözümü belgelemek için modelleme yapıyoruz. Modelin problemin çözümü için yeterli düzeyde detayı içeriyor olması gerekir. Eğer model eksik ise problemin doğru çözümünü elde edemeyiz, model gereğinden fazla detay içeriyor ise fazladan mühendislik yaptığımız için vakit kaybederiz, bütçeyi gereksiz yere arttırmış ve insan kaynağını verimsiz bir şekilde kullanmış oluruz.
Bir yazılım projesinde tek bir model bulunmaz. Çok sayıda model vardır. Yukarıda gördüğümüz BTS'nin ve İSS'nin ayrı ayrı modelleri vardır. BTS için yine birden fazla model oluştururuz. Yazılım yaşam döngüsünün her bir evresi için bir model yaratırız. Örneğin müşteriden isterlerin alınması evresinin sonunda ister modeli oluşur. Her bir evre sonunda oluşan model izleyen evreler için girdi olarak kullanılır. Örneğin ister modeli çözümleme evresi için girdi olarak kullanılır ve evrenin sonunda analiz modeline evrilir. Analiz modeli tasarım evresinde tasarım modeline, mimari tasarım evresinin sonunda ise mimari modele dönüşür. Mimari modeller yüksek seviyeli modellerdir ve bileşenlerin biri birilerine nasıl bağlanacağını modeller. Sıklıkla kullandığımız mimari modellere istemci-sunucu mimarisini, n-katmanlı mimarisini, Model-View-Controller (MVC) mimarisini, Servis Odaklı Mimariyi (SOM) örnek olarak verebiliriz. Mimari modeller genellikle bize bir şablon verirler. İşlevsel isterler tasarım modelini işlevsel olmayan isterler ise mimari modeli şekillendirir. Çözüm modelini ise tasarım modelini mimari modelin sağladığı bu şablona uygulayarak elde ederiz.
Süreç yönetim modeli ise bir başka modeldir. Yazılım yaşam döngüsünün nasıl yönetileceğini, hangi rolün hangi etkinliklerde yer alacağını, ne zaman neyi yapacağını modeller. Eğer yinelemeli bir süreç yönetim modeli kullanılıyor ise her bir model her yinelemede yeni bir sürüme sahip olacaktır. Dolayısı ile her model yinelemeler boyunca değişebilir ve yeni bir sürüme sahip olabilir.
Bir problem verildiğinde modeli nasıl elde edileceğine ilişkin genel geçer bir yöntem bulunmamaktadır. Genellikle nesneye dayalı bir yaklaşım kullanılarak çözümleme ve tasarım yapılmaktadır. Nesneye dayalı programlamada üç temel yapı bulunmaktadır. Her nesneye dayalı programlama dili bu üç yapıyı destekler:
  1. Paketleme (=Encapsulation)
Nesneye dayalı programlamada, modelleme sınıflar (=Class) aracılığı ile sağlanır. Sınıf veri ve sadece o veri üzerinde işlem yapan metotların bir araya getirilmesi ile oluşturulur. Sınıfın iki tür üyesi bulunur: öznitelikler ve metotlar. Öznitelikler bazen veri, bazen durum olarak da adlandırılır ve sınıfın durağan davranışını modeller. Örneğin çemberi modellemek üzere bir sınıf yazmamız gerekse merkez koordinatları (x,y) ve yarıçap radius bu sınıfın öznitelikleri olacaktır. Bu sınıf Java programlama dilinde kodlanırsa kod aşağıdaki gibi olacaktır:
public class Circle {
     private double x,y ;
     private double radius;
}
Bu model doğru mudur? Doğru, yanlış ya da eksik olduğunu ancak tanımlandığı bağlama bakarak karar verebiliriz. Eğer bu sınıf geometri hesaplamalarında kullanılacak ise doğrudur. Ancak bir çizim programında kullanılmak üzere modellenmiş ise eksiktir. Buna kalınlık, çizim stili, renk gibi öznitelikler eklemek gerekir:
public class Circle {
     private double x,y ;
     private double radius;
     private Color color;
     private int thickness;
     private int style;
}
Bir çizim problemi için yukarıda sınıf daha doğru bir modelleme sağlayacaktır. Metodlar ise sınıfın dinamik davranışı modeller. Örneğin çember sınıfı için area() fonksiyonu çemberin alan hesabını tanımlar:
public class Circle {
     private double x,y ;
     private double radius;
     public double area(){
        return Math.PI * radius * radius;
     }
}
Ancak yukarıda olduğu gibi eğer bir çizim yazılımı için modelleme yapılacak ise draw() metodu daha doğru bir modelleme sunar. Nesneye dayalı programlamanın soyutlama gücü yüksektir. Problemin tanımlandığı fiziksel dünya ile sınıfları kullanarak bilgisayarda kurduğumuz modeli biri birine yaklaştırır. Böylelikle geliştiricinin zihinsel modeli ile gerçeklik arasındaki algılama ya da kavrama uzaklığı azalacaktır. Bu ise problemin daha rahat anlaşılmasını, tasarlanmasını, kodlanmasını ve test edilmesini sağlar.
Sınıflar problemin çözümü için yeterli değildir. Sınıf, inşat mühendislerinin binayı inşa etmeden önce yaptıkları taslak plan çizimleri gibidir. Taslak planda oturamazsınız, yaşayamazsınız. Mutlaka o taslak plana uygun bir şekilde binanın inşa edilmesi gerekir. Sınıflar da aynen taslak planlar gibidir. Problemi çözebilmek için sınıflardan nesneler yaratmak ve bu nesnelerin metotlarını belirli bir sırada ve uygun parametreler ile çağırmak gerekir. Nesneler yaşayan varlıklardır. Sınıflar ise aynı türden nesneler için bir şablon tanımlar. Aynı sınıftan nesnelerin aynı özellikleri vardır, ancak durumları, bu özelliklerin aldıkları değerler farklı olabilir. Aşağıdaki örnekte unitCircle ve anotherCircle nesnelerinin her ikisi de Circle tipindendir, aynı özelliklere sahiptir, ancak durumları farklıdır. unitCircle nesnesi birim çemberdir, diğer nesne anotherCircle ise merkezi (1.0,-1.0) noktasında olan 2.0 yarıçaplı bir çemberdir:
            {
        Circle unitCircle= new Circle(0.0,0.0,1.0);
        Circle anotherCircle= new Circle(1.0,-1.0,2.0);
     }
Nesneye dayalı bir yaklaşımla tasarlanmış bir yazılımın çalışma zamanında resmini çekebilsek karşımıza bir nesne dokusu (ağı) çıkacaktır. Bu dokudaki nesnelerin durumunun bozulmasını istemeyiz. Nesneler bozulduğunda bakım aşamasında hata ayıklama etkinliği ile önce hatanın yerini bulmalı ve düzeltmeliyiz. Keşke hiç bozulmayan nesnelerimiz olabilse. Paketleme bozulmayan sınıflar tasarlamak için tek başına yeterli değildir. Bunun için veri gizleme ilkesini de uygulamak gerekir. Veri gizleme ilkesine göre, sınıfın verilerine doğrudan erişime kapatılmalı ve kontrollü erişim için ise dışarıya açık metotlar eklenmelidir. Bu metotlar nesneyi hatalı bir duruma gitmesine izin vermeyecek şekilde tasarlanmalıdır. Bu ilkeyi Circle sınıfına uygulanacak olursa aşağıdaki gibi çözüm elde edilir:
package com.example.domain;

public class Circle { // Immutable Class

    private double x, y;
    private double radius=1.0;

    public Circle(double x,double y,double radius) {
        this.x = x;
        this.y = y;
        if (radius > 0) {
            this.radius = radius;
        }
    }

    public double getX() {
        return x;
    }

    public double getY() {
        return y;
    }

    public double getRadius() {
        return radius;
    }

    public double area() {
        return Math.PI * radius * radius;
    }
}
Bu sınıftan yaratılacak hiçbir çember nesnesi geçersiz duruma gitmeyecektir. Ne yapılırsa yapılsın negatif yarıçaplı bir çember elde etmek mümkün olamaz.
Paketleme ve veri gizleme ilkesinin beraber kullanımının bir faydası da Kapalı Kutu (Black-Box) tasarıma olanak vermesidir. Sınıfın dışarıya açık metotlarının imzası (metodun adı, aldığı parametreler ve tipleri) değiştirilmeden sınıfın dışarıya kapalı mimarisi ya da yapısı değiştirildiğinde, bu sınıftan hizmet alan diğer sınıflarda bir değişiklik yapmak gerekmez. Metotların imzalarında değişiklik yapmadan metodun içini değiştirmeye yazılım mühendisliğinde yeniden yapılandırma (=Refactoring) olarak adlandırıyoruz. Bir sınıfı ya da metodu daha hızlı ve kararlı çalışması, daha güvenilir sonuç vermesi, daha az bellekle çalışması, çok-çekirdekli sistemlerde daha verimli çalışması ya da daha güvenli olması gibi nedenlerle yeniden yapılandırabiliriz.
Paketleme ve veri gizleme ilkesinin diğer bir faydası da hatayı bulmayı kolaylaştırmasıdır. Bir nesne hatalı bir duruma gittiğinde hatayı tüm kodda aramamız gerekmez. Kodun daha küçük bir bölgesinde arama yapmamız yeterli olacaktır. Örneğin aramayı hatalı nesnenin sınıfı içindeki metotlara, kalıtım ilişkisi olan sınıflara ya da bu sınıfın arkadaş sınıflarına indirgeyebiliriz. Java’da aynı paketteki sınıflar biri birlerinin arkadaşları olarak adlandırılır. Arkadaş sınıflar biri birlerinin default tanımlı üyelerine erişebilirler. Java’da bir üye tanıtılırken public, protected ve private erişim denetim sözcüklerinden herhangi biri kullanılmamışsa default erişime sahip olur.
  1. Kalıtım (=Inheritance)
Her yeni projeye başladığımızda kendi kısıtları ile başlarız. Temel proje kısıtları olarak proje süresini, bütçesi ve insan kaynağını sıralayabiliriz. Üç yıllık bir proje başarı ile tamamlanmış olsun. Yeni bir projeye başlanıldığında, bu yeni projenin kendi kısıtları ile karşılaşılır. Bu kısıtları ortadan kaldırmak mümkün değildir, ancak kısıtlar hafifletilebilir. Eğer daha önceki projelerde yazdığımız sınıfları yeni projelerde de kullanabilirsek aynı yazılımı daha kısa sürede ya da daha az bütçe ile ya da daha az insan kaynağı ile tamamlayabiliriz. İşte nesneye dayalı programlamadaki kalıtım mekanizması daha önceki projelerde kodladığımız, test ettiğimiz sınıfları bazı değişiklikler yaparak yeni projelerde de kullanabilmemize olanak sağlar. Dolayısı ile bir problemi çözerken tasarladığımız sınıfları sadece elimizdeki problemi çözmek için değil ileride başka projelerde de kullanabilir miyim sorusunu sorarak tasarlamalıyız. Yazılım mühendisliği açısından buna tekrar kullanılabilirlik (=Reusability) diyoruz.
  1. Çok Şekillilik (=Polymorphism)
Nesneye dayalı programlamanın en önemli ayağı çok şekilliliktir. Yazılım mühendisliği açısından çok şekillilik çeviklik sağlar. Üç yıllık bir proje başarı ile tamamlanmış olsun. Yazılımın diğer mühendisliklerin ürünlerinden farklı olduğu birkaç nokta vardır. Bu noktalardan biri teslim ettiğimiz yazılım üzerinde müşterinin her zaman değişiklik isteğinde bulunabilmesidir. Örneğin inşaat mühendisliğinde bir köprü inşası tamamlanıp teslim edildikten sonra köprünün konumunu 10 kilometre kuzey doğuya taşımak, gidiş-dönüş yönünde birer şerit eklemek gibi istekler kabul edilemez. Aynı durum yazılım için çok geçerli değildir. Önemli olan bu değişiklik isteğine ne kadar sürede cevap verebildiğimiz, ne kadar çevik olabildiğimizdir. Eğer basit ya da karmaşık bir değişiklik isteğine bir yılda cevap veriyorsak tasarımda bir problem var demektir. Muhtemelen kodun büyük bir kısmında değişikliğe gitmek zorunda kalmışızdır. Çok şekillik, sınıf hiyerarşisine birkaç sınıf eklemek ve değiştirmek istediğimiz davranışı, metodu yeniden tanımlayarak değişiklik isteğine hızlıca karşılık vermemize olanak sağlar.

1 comment: