Java Kodunuzun Nesne-Merkezli Olmadığının 10 İşareti – VII: Çok sayıda metoda sahip olan sınıflar

Geliştirdiğimiz Java kodunun nesne-merkezli olmadığının 10 işaretinin neler olduğunu bu yazıda listelemiş ve ilk beş maddeyi ele almıştık. Şimdi de altıncı maddeyi ele alalım:

Çok sayıda metoda sahip olan sınıflar. (Classes with lots of methods.)

Sınıflarla soyutlamalar yapılır. Sınıflar, sorumlulukları ve bilgiyi sarmalar (encapsulation). Sorumluluklar ise metotlarla yerine getirilir. Dolayısıyla çok sayıda metot, çok sayıda sorumluluğa sahip olmak anlamına gelir. Sınıflar ise çok sorumluluğa sahip olmamalıdırlar. Sağlıklı sınıflar az sorumluluğa sahip sınıflardır.

Genel olarak karmaşıklık arttıkça birlikteliğin (cohesion) düştüğü kabul edilir. Çünkü karmaşık yapılar muhtemelen birden fazla şeyi bir arada hallediyorlardır. Yani karmaşık bir yapı, aslında aralarında ilişkiler olan daha basit bir kaç yapı olarak ifade edilmelidir. Bir sınıfın çok sayıda metoda sahip olması da onun karmaşıklaşması anlamına gelir. Eğer sınıfın soyutladığı kavramla ilgili çok sorumluluğu varsa, farklı sorumluluk kümeleri oluşturulmalı ve bu kümeler farklı sınıflar altında soyutlanmalıdır. Bu durumda yapılacak şey, bir sınıfa sahip olmak yerine aralarında ilişkiler bulunan birden fazla arayüz ve sınıfa sahip olmaktır.

Aslında metotlar için söylediğimiz bu durum nesnenin değişkenleri için de geçerlidir. Çok sayıda değişkene sahip olan sınıflarda da bir odaklanma problemi olduğu söylenebilir. Dolayısıyla sınıflar, değişken açısından da olabildiğinda az sayıda değişkene sahip olmalı ve bu değişkenler sınıfın soyutlamasının bir parçası olmalılar.

Tahmin edeceğiniz gibi burada bahsettiğimiz metotlar ve değişkenler, nesnelerin metotları ve değişkenleridir. Zira zaten bir sınıfta normal olarak statik değişken ve metotların sayısının çok olması o sınıfın bir soyutlama yapmayan, ancak utility vb. tipte bir sınıf olması durumunda geçerlidir.

Bir sınıfı modellerken akılda tutulması gereken ilk prensip muhtemelen “Tek Sorumuluk Prensibi”dir. “Single Responsibility Principle” diye bilinen bu prensibe göre sınıflarımız sadece ve sadece bir sorumluluk alanına sahip olmalıdır. Yani sınıflarımız sadece bir konuya odaklı olmalı, onunla ilgili sorumlulukları içermeli ve sadece sebepten dolayı değişmelidir. Sınıflarınızı kurgularken eğer birden fazla noktayı bir araya getiriyorsanız, metotlarınız arasında farklı konularla ilgili olanlar da varsa muhtemelen sınıfınız metot ve değişken sayısı yönünden çok zengin olacak ve bundan dolayı da düşük bir birlikteliğe sahip olacaktır.

Sağlıklı sınıflar elde etmek için aklımızda tutmamız gereken bir diğer prensip de “Interface Segregation Principle” yani “Arayüzlerin Ayırımı Prensibi” olabilir. Buna göre bir sınıfın clientları, o sınıfta kullanmadığı metotlar görmemelidir. Yani, sınıflarımızın arayüzlerini olabildiğince küçültmeli yani az sayıda metoda sahip olacak şekilde kurgulamalıyız. Bu amaçla eğer bir sınıf farklı sorumuluk gruplarına sahipse bu grupları farklı arayüzlerle ifade edecek şekilde ayırmalıyız.

Bu konuyu daha iyi açıklamak için şöyle örnekler verebiliriz. Bir Modem sınıfına ihtiyacımız olduğunu düşünelim. Bu sınıf önce bağlantı kuracak, sonra bağlantı üzerinden veri alış-verişi yapacak, sonra da bağlantıyı kapatacaktır. Bu amaçla şöyle bir sınıf tasarladığımızı düşünelim:

public interface Modem {
	public void dial(String pno);
	public void hangup();
	public void send(String s);
	public char receive();
}

Yukarıdaki Modem arayüzünün, temelde ayrı olarak ele alınması gereken iki farklı sorumluluk kümesini bir araya getirdiğini görüyoruz: Bağlantı kurmak ve bağlantıyı kapatmak ile veri alış-verişi aslında iki ayrı sorumluluk alanı. Eğer modem’i bu şekilde kurgularsak, modemi değiştirmek içın iki ayrı sebebimiz olacak. Öte taraftan, modem olmadığı halde bağlantı yöneten yapılar ile benzer şekilde modem olmadığı halde veri alış-verişi yapan yapıları bu yapıyla temsil etmemiz mümkün olamayacak. Yukarıdaki yapıyı iki ayrı arayüz olarak kurgulamak, tek sorumluluk prensibi ve birliktelik açısından daha iyi olurdu:

public interface Connection {
	public void dial(String pno);
	public void hangup();
}

 

public interface Communication {
	public void send(String s);
	public char receive();
}

 

public class Modem implements Connection, Communication {
	public void dial(String pno){ ... }
	public void hangup(){ ... }
	public void send(String s){ ... }
	public char receive(){ ... }
}

Bu şekilde sadece 4 metoda sahip bir yapıyı bile tek sorumluluk prensibi ile bölebiliyorsak, onlarca metoda sahip olan aşağıdaki sınıf için ne düşünürsünüz?

public class SuperDashboard extends JFrame implements MetaDataUser {
 public String getCustomizerLanguagePath()
 public void setSystemConfigPath(String systemConfigPath)
 public String getSystemConfigDocument()
 public void setSystemConfigDocument(String 
                                          systemConfigDocument)
 public boolean getGuruState()
 public boolean getNoviceState()
 public boolean getOpenSourceState()
 public void showObject(MetaObject object)
 public void showProgress(String s)
 public boolean isMetadataDirty()
 public void setIsMetadataDirty(boolean isMetadataDirty)
 public Component getLastFocusedComponent()
 public void setLastFocused(Component lastFocused)
 public void setMouseSelectState(boolean isMouseSelected)
 public boolean isMouseSelected()
 public LanguageManager getLanguageManager()
 public Project getProject()
 public Project getFirstProject()
 public Project getLastProject()
 public String getNewProjectName()
 public void setComponentSizes(Dimension dim)
 public String getCurrentDir()
 public void setCurrentDir(String newDir)
 public void updateStatus(int dotPos, int markPos)
 public Class[] getDataBaseClasses()
 public MetadataFeeder getMetadataFeeder()
 public void addProject(Project project)
 public boolean setCurrentProject(Project project)
 public boolean removeProject(Project project)
 public MetaProjectHeader getProgramMetadata()
 public void resetDashboard()
 public Project loadProject(String fileName, String projectName)
 public void setCanSaveMetadata(boolean canSave)
 public MetaObject getSelectedObject()
 public void deselectObjects()
 public void setProject(Project project)
 public void editorAction(String actionName, ActionEvent event)
 public void setMode(int mode)
 public FileManager getFileManager()
 public void setFileManager(FileManager fileManager)
 public ConfigManager getConfigManager()
 public void setConfigManager(ConfigManager configManager)
 public ClassLoader getClassLoader()
 public void setClassLoader(ClassLoader classLoader)
 public Properties getProps()
 public String getUserHome()
 public String getBaseDir()
 public int getMajorVersionNumber()
 public int getMinorVersionNumber()
 public int getBuildNumber()
 public MetaObject pasting(
 MetaObject target, MetaObject pasted, MetaProject project)
 public void processMenuItems(MetaObject metaObject)
 public void processMenuSeparators(MetaObject metaObject)
 public void processTabPages(MetaObject metaObject)
 public void processPlacement(MetaObject object)
 public void processCreateLayout(MetaObject object)
 public void updateDisplayLayer(MetaObject object, 
                                                 int layerIndex)
 public void propertyEditedRepaint(MetaObject object)
 public void processDeleteObject(MetaObject object)
 public boolean getAttachedToDesigner()
 public void processProjectChangedState(boolean 
                                        hasProjectChanged)
 public void processObjectNameChanged(MetaObject object)
 public void runProject()
 public void setAçowDragging(boolean allowDragging)
 public boolean allowDragging()
 public boolean isCustomizing()
 public void setTitle(String title)
 public IdeMenuBar getIdeMenuBar()
 public void showHelper(MetaObject metaObject, 
                        String propertyName)
 // ... many non-public methods follow ...
}

Yukarıdaki sınıfı R. Martin’in Clean Code kitabından aldım ama emin olun bu sınıf kadar odaksız, gelişi güzel yazılmışlarını ben de çok gördüm. Danışmanlıklarım sırasında o kadar baştan savma, hiç bir tasarım kırıntısı taşımayan ve sonrasında da farklı faktörlerle büyümüş, devasa boyutlara ulaşmış sınıflar gördüm ki.

Martin, Clean Code kitabında sınıflar hakkında şöyle der:

“The first rule of classes is that they should be small. The second rule of classes is that they should be smaller than that.”

Yani

“Sınıflarla ilgili ilk kural, onların küçük olması gerektiğidir. Sınıfların ikinci kuralı ise onların bundan da küçük olması gerektiğidir.”

“Refactoring in Large Software Projects” isimli kitapda da  M. Lippert ve S. Roock, mimari kötü kokulardan bahsederken bir sınıfta ortalama olarak 30’dan az metot olması gerektiğinden bahseder. (Lippert ve Roock kitaplarında genelde bir seviyede, alt seviyedeki elementlerden en çok 30 tane olması gerektiğini söylerler. Bir pakette 30 tip, bir tipte 30 metot, gibi.)

Bir kodu iyileştirmek istediğimde ilk yapmak istediğim şey genelde o kodu, küçük parçalara bölmektir. Büyük sınıfları, küçük sınıflara, büyük metotları küçük metotlara çevirmek, o kod parçasını çok daha anlaşılır, test edilir ve değiştirilebilir yapacaktır.

Bir kod kalitesi aracı olan PDM de “code size” kalite ölçeğinde birden fazla büyüklük ölçümüne sahiptir. Bu ölçümlerden “TooManyMethods”un ölçum sınırı 10’dur. Yani PDM, 10’dan fazla metoda sahip sınıflar için uyarı vermektedir. PMD benzer şekilde 15’ten fazla alana sahip olan sınıflar için de “TooManyFields” uyarısı üretmektedir. Bu durum da, en başta da belirttiğimiz gibi sonuçta bir odaklanma probleminin işaretidir.

Metot sayısının çokluğu açısından eleştirilemeyecek sınıfların olmasını düşünmek anormal bir durum değildir. Muhtemelen teme veri yapıları ya da iş nesneleri cinsinden en temel soyutlamaların, daha fazla syıda metota sahip olma eğiliminde oldukları söylenebilir. Bu duruma bol miktarda oveloaded metotlara sahip olan utility sınıfları da dahildir.

İşin açıkçası, bir sınıfın kalitesini sadece metot sayısına indirgemek itiraz edilecek bir durumdur. Örneğin Java SE’nin 8 sürümündeki java.lang.String sınıfı üzerinde 15 constructor ve 15’i statik, 52’si nesne metodu olmak üzere toplam 67 tane metot vardır. Bu metotlardan sadece bir tanesi deprecated olarak işaretlenmiştir. java.lang.Object sınıfından devralınan ve bu sınıfta override edilen metotları çıkarsak bile String sınıfının 60 civarında metoda sahip olduğu söylenebilir. Benzer durum java.lang.Math sınıfı için de geçerlidir. Math sınıfındaki tüm metotlar statiktir ve toplam 73 tanedir. Peki bu duruma ne deriz? Aslında metot ya da satır sayısı cinsinden tamamen sayısal değerlendirmelerin, bir sınıfın kalitesi hakkında her şeyi söylemediğini, daha karmaşık ve anlama yönelik kalite kriterlerinin olması gerektiğini ifade edebiliriz. Bu durum zaten kaynaklarda da tartışılmakta ve farklı önerilere ve çalışmalara konu olmaktadır. Bence çok bilimsel çalışmadığımız durumlarda, tersini anlayıncaya kadar büyüklük bir kötü koku sebebidir, üzerine gitmekte fayda vardır.

Schumacher’in dediği gibi “küçük güzeldir” (small is beautiful) ve büyük olmanın işe yarayacağını kanıtlayıncaya kadar sınıflarımızı küçük tutalım.

Toplam görüntülenme sayısı: 933