Clean Code — Chapter 8: Boundaries

Veysel Mutlu
7 min readJun 10, 2023

Merhabalar,

Bu yazıda Robert Cecil Martin’in yazdığı Clean Code kitabının ‘Boundaries’ bölümünden öğrendiklerimi sizinle paylaşacağım. Bu yazı daha çok kitabın türkçe özeti şeklindedir.

SUMMARY — Overview

“Clean Code” kitabının 8. bölümü, “boundaries” yani “sınırlar” konusuna odaklanmaktadır. Bu sunumda, yazılım bileşenleri arasındaki sınırların nasıl düzenlenmesi gerektiği ve dış kaynaklarla nasıl etkileşimde bulunulması gerektiği üzerinde durulmaktadır.

Sınırlar, yazılım bileşenlerinin birbirleriyle etkileşimini kontrol etmek ve düzenlemek için kullanılan mekanizmalardır. Bu bölümde, dış kaynaklarla olan etkileşimin nasıl soyutlanacağı, sınırların nasıl net ve tutarlı bir şekilde belirleneceği, sınırları geçmekten kaçınılması gerektiği ve sınırların nasıl test edileceği gibi konular ele alınacaktır.

Yani, “Clean Code” kitabının bu bölümünde, yazılım bileşenleri arasındaki sınırların yönetimini ve dış kaynaklarla olan etkileşimi ele alarak, bu sınırların nasıl oluşturulması, korunması ve test edilmesi gerektiğine değineceğiz.

Sistemlerimizde genellikle tüm yazılımları kontrol etmeyiz. Bazen üçüncü taraf paketler satın alır veya açık kaynaklı yazılımlar kullanırız.

Diğer zamanlarda ise, kendi şirketimizdeki ekiplerin bize bileşenler veya alt sistemler üretmesine bağımlı oluruz. Bu yabancı kodu kendi kodumuzla sorunsuz bir şekilde entegre etmemiz gerekmektedir.

Bu bölümde, yazılımımızın sınırlarını temiz tutmak için uygulamalar ve teknikler incelenecektir.

Using Third-Party Code

Üçüncü taraf paketlerin(packages) ve çatıların (framework) sağlayıcıları, yazılımlarının geniş kitlelerce kullanılabilir olmaları ve bir çok ortamda çalışabilir olmaları için çabalarlar. Diğer bir taraftan kullanıcılar ise belirli ihtiyaçlara odaklanmış arayüzlerle çalışmak isterler.

Örnek olarak java.util.Map’ı interfacesini ele alalım;

Yandaki şekli (Şekil 8–1’i) inceleyerek görebileceğiniz gibi, Map bir çok şey yapabilen metotlarla dolu bir arayüzdür. Bu güç ve esneklik yeri geldiğinde kullanışlıdır ancak bir çok sorumluluğu da beraberinde getirir.

Örneğin uygulamamızda bir Map kullanıyor ve onu bir yerlere argüman olarak geçiyor olalım. Map’imizi kullananların, içerisinden hiçbir şey silmemesi gerekebilir.

Ancak Map arayüzü clear() metoduna sahiptir ve Map’i kullanan herkes bu güce sahiptir. Ya da tasarımımıza göre Map üzerinde sadece belirli tipte nesneleri tutuyor olabiliriz. Fakat Map, içindeki nesnelerin tiplerini güvenilir şekilde sınırlandırmaz. Herhangi bir kullanıcı, herhangi tipte bir nesneyi Map’e ekleyebilir.

Uygulamamızın sensörlerden oluşan bir Map’e ihtiyacı olsaydı, muhtemelen şu şekilde tanımlayacaktık:

Daha sonra bir yerlerde tek bir sensöre erişmek isteseydik, şu şekilde erişecektik:

Tamam bu çözüm işe yarar ancak kod temiz değildir. Bu kod bir Map üzerinden bir Object’e erişmenin ve onu doğru tipe dönüştürmenin (type casting) sorumluluğunu taşıyor. Kodun okunabilirliğini Generic’leri kullanarak büyük oranda geliştirebiliriz:

Map<Sensor> yapması gerekenden ya da istediğimizden daha fazla şey yapıyor ve bu çözüm de problemimizi çözmüyor.

Map<Sensor> örneğini (instance) serbestçe bir yerlere argüman olarak geçirmek demek, Map arayüzü her değişiklik yaptığında, düzeltilecek çok yerin olacağı anlamına gelir. Böyle bir değişikliğin pek olası olmadığını düşünebiliriz.

Map’in daha temiz bir kullanımı aşağıdaki gibidir.

İşte burada Map arayüzü Sensors sınırının (boundary) ardına gizlenmiştir. Uygulamanın geri kalanı üzerinde çok az etki bırakarak değiştirilebilir. Generic kullanımı artık büyük bir sorun değil çünkü tip dönüşümü ve tip yönetimi, Sensors sınıfının içinde halledildi. Bu arayüz sadece uygulamanın ihtiyaçlarını karşılaması için oluşturuldu.

Map’in her kullanımında bu şekilde kapsüllenmesini önermiyoruz. Bunun yerine, Map’leri (veya sınırdaki başka herhangi bir arayüzü) sisteminizde bir yerlere geçirmemenizi tavsiye ediyoruz. Map gibi bir sınır arayüzü kullanıyorsanız, onu sınıfın içinde tutun. Map’leri return etmekten ve public API’lere argüman olarak geçmekten kaçının.

Exploring and Learning Boundaries

Üçüncü taraf yazılımlar/kodlar, daha az zaman harcayarak daha fazla işlevsellik elde etmemize yardımcı olur. Üçüncü taraf kodunu test etmek bizim görevimiz değildir, ancak kullandığımız üçüncü taraf kodu için testler yazmak bizim çıkarımıza olabilir.

Üçüncü taraf kütüphanemizi nasıl kullanacağımızın net olmadığını varsayalım. Doküman okumak ve onu nasıl kullanacağımıza karar vermek için bir veya iki gün (veya daha fazla) harcayabiliriz. Ardından, kodumuzu üçüncü taraf kodu kullanacak şekilde yazabilir ve düşündüğümüz şekilde çalışıp çalışmayacağını görebiliriz. Hatta çoğu zaman kendimizi, aldığımız hataların kodumuzda mı yoksa onların hataları mı olup olmadığını anlamaya çalışırken buluruz.

Üçüncü taraf kodu öğrenmek ve entegre etmek zordur. İkisini birlikte yapmak daha da zordur. Üretim (production) kodumuzda yeni şeyler denemek yerine, üçüncü taraf kodu keşfetmek için bazı testler yazabiliriz. Jim Newkirk bu testleri, öğrenme testleri (learning tests) olarak nitelendiriyor.

Öğrenme testlerinde, dış kaynakların (örneğin API) uygulamamızda kullanmayı umduğumuz şekilde çağırırız. Aslında, bu dış kaynaklar hakkındaki anlayışımızı kontrol eden kontrollü deneyler yapıyoruz. Testler, kullandığımız dış kaynaktan ne istediğimiz üzerine odaklanır.

Learning log4j

Diyelim ki kendi özel oluşturduğumuz log kaydedici yerine apache log4j paketini kullanmak istiyoruz. Biraz doküman okuduktan sonra ilk testimizi yazarız ve konsola bir “hello” yazmasını bekleriz:

Testi çalıştırdığımızda logger, Appender adı verilen bir şeye ihtiyacımız olduğunu belirten bir hata üretir. Biraz daha okuduktan sonra bir ConsoleAppender olduğunu farkederiz.

ConsoleAppender da ekledikten sonra kod şu şekilde geliyor:

Bu kez, Appender’ın çıktı üretmediğini görüyoruz. Google’dan küçük bir yardım aldıktan sonra aşağıdakileri deneyelim:

İşe yaradı ve konsola “hello” yazdırdık. ConsoleAppender’a konsola yazdığını söylemek zorundayız.

İlginçtir ki, ConsoleAppender.SYSTEM_OUT bağımsız değişkenini kaldırdığımızda, “hello” ifadesinin yine de yazdırıldığını görüyoruz. Fakat PatternLayout’u çıkardığımızda, gene hata alıyoruz. Bu çok garip bir davranış. Dokümantasyona biraz daha dikkatli baktığımızda, varsayılan ConsoleAppender kurucusunun “yapılandırılmamış” olduğunu görüyoruz. Bu Log4j’de hata veya en azından bir tutarsızlık gibi görünüyor.

Aşağıdaki basit birim testini kodlayalım;

Artık basit bir konsol loggerını nasıl başlatacağımızı biliyoruz. Bu bilgiyi de kendi logger sınıfımıza koyarak, uygulamanın geri kalanının Log4j sınır arayüzünden izole edilmesini sağlayabiliriz.

Learning Tests Are Better Than Free

Öğrenme testleri sonunda hiçbir maliyeti olmaz. Zaten dış kaynağı (örneğin API’yi) öğrenmemiz gerekiyordu ve bu testleri yazmak, bu bilgiye kolay ve izole bir şekilde ulaşmanın bir yolu oldu. Öğrenme testleri, anlayışımızı artırmaya yardımcı olan kesin deneylerdi.

Üçüncü taraf paketin yeni sürümleri olduğunda, davranış farklılıklarını görmek için öğrenme testlerini çalıştırırız.

Öğrenme testleri, kullandığımız üçüncü taraf paketlerin beklendiğimiz şekilde çalıştığını doğrular. Bir kez entegre edildikten sonra, üçüncü taraf kodun ihtiyaçlarımızla uyumlu kalacağına dair garantisi yoktur. Orijinal yazarlar, kendi yeni ihtiyaçlarını karşılamak için kodlarını değiştirme baskısı altında olacaklardır. Hataları düzeltecek ve yeni yetenekler ekleyeceklerdir. Her yeni sürümle yeni riskler ortaya çıkar. Eğer üçüncü taraf paket, testlerimizle uyumsuz bir şekilde değişirse, hemen fark ederiz.

Öğrenme testlerinin sağladığı öğrenmeye ihtiyacınız olsa da olmasa da, temiz bir sınırlama, üretim kodunun kullandığı arabirimi aynı şekilde kullanacak bir çıkış test seti tarafından desteklenmelidir. Bu sınırlama testleri olmadan göçü kolaylaştıracak bir şekilde, eskisini daha uzun süre kullanma eğiliminde olabiliriz.

Using Code That Does Not Yet Exist

Bilinen ile bilinmeyen arasında ayrılan başka bir sınır daha vardır. Kodun bazen bilgimizin sona erdiği yerler vardır. Bazen sınırın diğer tarafı bilinemeyen bir şeydir. Bazen sınırdan daha ileriye bakmamayı seçeriz.

Birkaç yıl önce bir radyo iletişim sistemi için yazılım geliştiren bir ekibin bir parçasıydım. “Verici” adında bir alt sistem vardı ve alt sistemden sorumlu kişiler arayüzlerini (API’lerini) tanımlama noktasına gelmemişlerdi. Engellenmek istemedik, bu yüzden çalışmamıza kodun bilinmeyen kısmından uzak bir noktadan başladık. Dünyamızın nerede bittiği ve yeni dünyanın nerede başladığı konusunda oldukça iyi bir fikrimiz vardı. Çalışırken bazen bu sınırı aştık. Sisler ve bilgisizlik bulutları, sınırdan ötesini görüşümüzü engelliyor olsa da, çalışmalarımız bize sınır arayüzünün nasıl olmasını istediğimizi fark ettirdi.

Vericilere şöyle bir şey söylemek istedik:

Vericiyi sağlanan frekansa girin ve bu akıştan gelen verinin analog bir temsilini yayınlayın

Bunun nasıl yapılacağını bilmiyorduk çünkü API henüz tasarlanmamıştı. Bu yüzden ayrıntıları daha sonra çalışmaya karar verdik. Engellenmekten kaçınmak için kendi arayüzümüzü tanımladık.

Buna Transmitter ismini verdik. Frekans ve veri akışını alan transmit adında bir metod imzası ekledik. Bu, sahip olmak istediğimiz arayüzdü. Sahip olmak istediğimiz arayüzü yazmanın iyi bir yanı, onun kontrolümüz altında olmasıdır.

Bu, istemci kodunu daha okunabilir ve amacına odaklanmış tutmaya yardımcı oldu.

Yandaki şekilde (Şekil 8–2’de), CommunicationsController sınıflarını (kontrolümüz dışında ve tanımlanmamış olan verici API’sini) yalıttığımızı görebilirsiniz. Kendi uygulama spesifik arayüzümüzü kullanarak CommunicationsController kodumuzu temiz ve anlamlı tuttuk.

Verici API’si tanımlandığında, köprüyü kurmak için TransmitterAdapter’ı yazdık. Bu ADAPTER, API ile etkileşimi kapsüller ve API geliştikçe değiştirilecek tek bir yer sağlar.

Yukarıdaki tasarım aynı zamanda test etmek için kodumuzda çok uygun bir yer sunar.

Uygun bir FakeTransmitter kullanarak CommunicationsController sınıflarını test edebiliriz. Ayrıca, TransmitterAPI’ye sahip olduktan sonra, API’yi doğru şekilde kullandığımızı sağlayan sınır testleri oluşturabiliriz.

Clean Boundaries

Sınırlarda değişimler olur. İyi yazılımlar, büyük yatırımlar ve yeniden çalışmalar olmadan değişikliklere uyum sağlarlar. Kontrolümüz dışında olan kodları kullandığımızda, yatırımımızı korumak için özel bir dikkat göstermeli ve gelecekte yapılacak değişikliklerin çok pahalı olmayacağından emin olmalıyız.

Sınırlardaki kod, beklentileri tanımlayan kesin ayırımlara ve testlere ihtiyaç duyar. Kodumuzun üçüncü taraf yazılımların ayrıntılarıyla ilgili çok fazla şey bilmesini önlemeliyiz. Kontrol etmediğimiz bir şeyden çok, kontrol ettiğimiz bir şeye güvenmek daha iyidir, çünkü sonunda o bizi kontrolü altına alacaktır.

Referanslar

  1. Robert C. Martin, Clean Code Kitabı

--

--