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:
- 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.
- 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.
- Ç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.