Kod olağandışı koşullarda dahi doğru çalışmalı

Bir kod parçasının doğru çalışmasını sağlamamız gerektiği aşikâr. Çoğu zaman kod yazarken yalnızca olağan koşulları düşünüyoruz. Doğal olarak yazdığımız kod da doğru çalışıyor — tabii olağan koşullarda. Fakat bu sistemde bir problem var: Hayat her zaman olağan koşullarda ilerlemiyor.

Bir otomobil ürettiğimizi düşünelim. Dünyanın geri kalanını unutun; diyelim ki bu otomobil sadece Türkiye’de kullanılacak. Meteorolojiye danışalım ve ülkemizin yıllık ortalama sıcaklık değerlerini alalım: Yıl boyu en yüksek ortalama sıcaklık 21°C, en düşük ortalama sıcaklık ise 0°C. Örneği basit tutmak için, çok da bilimsel olmayan bir şekilde, aradaki farkı kullanarak kendimize uç sıcaklıkları çıkaralım:

  • Maksimum: 42°C
  • Minimum: -21°C

Diyelim ki bunlar Türkiye için yıl boyu olağan sıcaklık aralığı. Dolayısıyla da, otomobilimizi olağan koşullar için ürettiğimizde bu sıcaklık aralığında çalışması için üretmiş oluyoruz.

Peki, diyelim ki, olağandışı bir durum oldu. Eğer meteorolojinin yukarıda verdiğim linkine giderseniz, Türkiye’de kaydedilmiş en yüksek sıcaklığın 49°C, en düşük sıcaklığın ise -46°C olduğunu görürsünüz. Yani, otomobilimizi olağan koşullar için üretirsek, bu sıcaklık değerlerinde çalışmadığını göreceğiz.

Elbette olağandışı koşulları düşünürken kâr-zarar dengesini göz ardı etmemek gerekir. Bir otomobil üretirken, -46°C’de çalışmasını sağlamak için maliyeti yükseltmek çok mantıklı olmayabilir. Fakat -46°C’de zor bir durumdayken çalışmayan bir ambulansla baş başa kaldığınızı düşünebiliyor musunuz?

Örnek

Yukarıdaki otomobil-ambulans örneğindeki gibi, özellikle önemli özellikler için, kodumuzun olağandışı koşullarda dahi çalışmasını sağlamalıyız. Aşağıdaki örneği ele alalım:

<?php

class Book // Kitap
{
   // price: Fiyat
   private $price;

   // buy: Satın al
   // discount: İndirim
   public function buy(int $discount = null): bool
   {
      if ($discount === null) {
         // pay: Ödeme yap
         return pay($this->price);
      }

      return pay($this->price - $discount);
   }
}

Yukarıdaki örnekte, tabii ki örneği sade tutmak için, buy yani Satın al anlamında sadece bir fonksiyon bulunuyor. Bu fonksiyon, tercihen bir de $discount adında bir indirim miktarı alabiliyor. Yani, çok basitçe, bir kitabı indirimsiz veya indirimi olarak satın alabiliyoruz.

Kod, olağan koşullarda gayet güzel çalışıyor. Eğer indirim yoksa, kitabı normal fiyatından satıyor. Eğer indirim varsa, indirim miktarı kitabın fiyatından düşüyor ve kitabı indirimli fiyattan satıyor.

Peki, oldu da oldu, bir şekilde indirim miktarı kitabın fiyatından yüksek geldi. Kitabın fiyatı 7 liraydı, ama indirim olarak 10 lira verdik. Böyle bir durumda, yukarıdaki kod ne yapacak? Olumlu düşünürsek, pay() fonksiyonu hata verecek ve satış gerçekleşmeyecek — ki bu durumda dahi kitap satış platformumuz hata verecek. Olumsuz düşünecek olursak, belki de pay() fonksiyonumuz, kitabı almaya çalışan kişiden -3 lira çekmeye çalışacak. Yani aradaki 3 lirayı, kitabı almaya çalışan kişiye gönderecek.

İşte böylece, olağandışı bir durumda, sadece kitap satışı yapmaya çalışırken şirketi batırabilecek bir bug’a sahip olduk. 🙂

Temizlik zamanı

<?php

class Book // Kitap
{
   // price: Fiyat
   private $price;

   // buy: Satın al
   // discount: İndirim
   public function buy(int $discount = null): bool
   {
      // totalPrice: Toplam fiyat
      $totalPrice = $this->price;

      if ($discount !== null) {
         $totalPrice - $discount;
      }

      if ($totalPrice <= 0) {
         throw new RuntimeException("Kitap ücreti 0'dan büyük olmalı!");
      }

      return pay($totalPrice);
   }
}

Yukarıda yaptığımız değişikliklerin üstünden geçelim:

  1. $totalPrice adında yeni bir değişken yarattık. Amacımız, tüm fiyat değişikliklerini bu değişken üzerinden yapmak ve sonuçta bu fiyatın ödemesini istemek.
  2. Eğer $discount, yani indirim verilmişse, bu indirim miktarını $totalPrice değişkenimize uyguladık.
  3. Eğer $totalPrice değişkenimiz 0’a eşit veya 0’dan küçükse, fonksiyonun çalışmasını bir istisnayla durdurduk ve ödeme almaya dahi yeltenmedik.
  4. Eğer istisna gerçekleşmediyse, yani toplam fiyat 0’dan yüksekse, ödeme aldık.

Yukarıdaki örnekler, bize kod yazarken biraz daha derin düşünmemizi öğütlüyor.

Tüm bunların yanında, olağandışı koşulları kontrol altında tutmanın daha iyi bir yolu da var: Otomatik testler. Test yazmak, olağandışı koşulları düşünmeyi kolaylaştırıyor. Testler hakkında bu yazıda ayrıntıya girmeyeceğim, fakat konu tekelinde testlerin oldukça yardımının olacağını söylemeden geçmek de istemiyorum.

Beklenebilecek davranışları yerine getirin

Eğer bir sınıf (class), bileşen (component) ya da paket (package) gibi gizli olmayan (public) sistemler kodluyorsanız, sistemden beklenen davranışları kodlamanız sisteminizi daha anlaşılır yapacaktır. Bu, sınıflar için bir fonksiyon olabilir. Ya da bir bileşen oluşturuyorsanız, içinde olması beklenebilecek bir sınıf da olabilir.

Biliyorum, böyle anlatınca oldukça soyut geliyor; fakat şöyle düşünün: Hangi dilde kod yazarsak yazalım, o dilin gizli olmayan fonksiyonlarını kullanabiliyoruz. Diyelim ki PHP dilinde kod yazıyoruz ve yazdığımız kod tarihlerin manipülasyonuyla ilgili. PHP’nin bu konu üzerine bir sınıfı mevcut: DateTime.

Yine diyelim ki, iki farklı tarihi birbiriyle karşılaştırmamız gerekiyor. İçgüdüsel olarak DateTime sınıfından beklentimiz ne olur? Şahsen, ben < ve > operatörleriyle karşılaştırma yapabilmeyi beklerim. PHP de zaten buna izin verir.

Bu gibi beklenen davranışların yerine getirilmediğini görmek, kodunuzu kullanan kişide (bu siz de olabilirsiniz!) güvensizliğe sebep olur. Güven bir sistem için çok önemlidir. Çünkü güvendiğiniz bir sistemi, sadece sınıf ya da fonksiyon isimlerine bakarak kullanırsınız. Güvenmezseniz, kullandığınız fonksiyonların doğru sonuç vereceğinden emin olmak için o fonksiyonların nasıl kodlandığını bilmeye eğilimli olursunuz. Bu yüzden kaynak kodu açıp okuyarak zaman kaybedersiniz.

Örnek

Yukarda oldukça kaba bir örnek verdim. Şimdi bir de kod üzerinden daha detaylıca inceleyelim:

<?php

class Book // Kitap
{
   // currentPage: Şimdiki sayfa
   private $currentPage;
   // pages: Sayfalar
   private $pages;

   // read: Oku
   public function read(): string
   {
      return $this->pages[$this->currentPage];
   }
}

Yukarıdaki örnekte sadece bir adet gizli olmayan fonksiyonumuz var: read(). Bu fonksiyonun yaptığı şey çok basit. Kitapta kaldığımız sayfayı ($currentPage) bize yazı olarak veriyor.

Diyelim ki birileri yukarıdaki sınıfı kodladı. Varsayın ki kitap sayfaları da $pages değişkeninde kayıtlı. Siz de bu sınıfı kullanarak bir arayüz hazırlıyorsunuz. Amacınız bir kitabın web üzerinden okunabilmesini sağlamak. Bu sınıfı olduğu gibi kullanarak amacınıza ulaşabileceğiniz bir yol görebiliyor musunuz? Göremezsiniz; çünkü yok.

Bu sınıf, örnek gereği oldukça kısa. Fakat gerçek hayatta karşılaştığımız kodlar çok daha uzun ve daha karmaşık. Sınıfı kodlayan yazılımcı beklenen davranışları yerine getirmediği için, şimdi siz tüm bu kodu okumak zorunda kalacaksınız. Sınıfın yazarına olan güveniniz sarsılacak.

Temizlik zamanı

Yukarıdaki sınıfta $currentPage‘in görünürlüğünü private‘tan public‘e çevirmek aslında sorunumuzu çözer. Bu değişken artık gizli olmayacağı için, kaldığımız sayfayı artırıp azaltarak okunacak olan sayfayı manipüle edebiliriz. Fakat bu, sınıfın daha da kirlenmesine sebep olur ve sizden sonra gelecek bir başka yazılımcının koda olan güveni bu kez sıfırlanır.

Bunun yerine yapabileceğimiz çok daha basit bir şey var: Beklediğimiz davranışı yerine getirmek. Burda beklediğimiz davranış nedir? Sayfa çevirebilmek. 🙂

<?php

class Book // Kitap
{
   // currentPage: Şimdiki sayfa
   private $currentPage;
   // pages: Sayfalar
   private $pages;

   // read: Oku
   public function read(): string
   {
      return $this->pages[$this->currentPage];
   }

   // previousPage: Önceki sayfa
   public function previousPage(): void
   {
      $this->currentPage--;
   }

   // nextPage: Sonraki sayfa
   public function nextPage(): void
   {
      $this->currentPage++;
   }
}

İki fonksiyon ekleyerek sınıfımızı tamamlamış olduk. Böylece, kitap okuma üzerine olan bir sınıf olan Book, sayfaları çevirebileceğimiz fonksiyonları da yerine getirmiş oldu.

İsimler yan etkilerden bahsetmeli

Tek Sorumluluk İlkesi’ni gözetmek için yapılabilecek en iyi şeylerden biri, bir elemana birden fazla sorumluluk yüklememektir. Bir değişkenin, fonksiyonun ya da sınıfın (class) tek görevi olmalıdır. Fakat yine de, bazen bunun aksini uygulamamızın daha mantıklı olacağı durumlarla karşılaşabiliriz. Böyle bir durumda yapılacak en iyi şey, elemanın birincil göreviyle birlikte ikincil görevini de anlatan bir isim kullanmak olacaktır.

Eğer bir değişken, fonksiyon ya da sınıf birden şey yapıyorsa, isimde bunu gizlemek, kodu okuyan herhangi birinin yanlış yönlenmesine sebep olur. İkincil görevi yan etki olarak tanımlarsak, isimlerde yan etkilerden bahsetmek kodu daha esnek yapar. Kim bilir, belki sonradan koda bakan bir çift göz, iki görevi olan bu elemanı iki farklı elemana çevirmenin bir yolunu bulur. Böylece, sonradan da olsa Tek Sorumluluk İlkesi’ne ulaşmanın temel taşlarından birini atmış oluruz. 🙂

Örnek

<?php

class Library // Kütüphane
{
   // mainLibrary: Ana kütüphane
   private $mainLibrary = null;

   // getMainLibrary: Ana kütüphaneyi al
   public static function getMainLibrary(): Library
   {
      if (self::mainLibrary === null) {
         self::mainLibrary = new Library();
      }

      return self::mainLibrary;
   }
}

Yukarıda basit bir örneğimiz var. Library sınıfımızda bir adet $mainLibrary adlı özelliğimiz (property), bir de bu özelliği döndüren getMainLibrary adında bir fonksiyonumuz var.

İlk bakışta, isimlendirmeler gayet yerinde görünüyor. Fonksiyonumuz, sınıfımızda tanımlı ana kütüphaneyi döndürmeye yarıyor. Fakat ne yazık ki iş o kadar basit değil.

Temizlik zamanı

Fonksiyonumuz getMainLibrary, ana kütüphaneyi döndürmeden önce $mainLibrary özelliğinin daha önce tanımlanıp tanımlanmadığını kontrol ediyor. Eğer tanımlanmamışsa, yeni bir kütüphane yaratarak bunu ana kütüphane, yani $mainLibrary olarak tanımlıyor. Daha sonra da bunu döndürüyor.

Bu, açık bir yan etki. Fonksiyonun birincil işi ana kütüphaneyi döndürmek; fakat tek işi bu değil. İkincil olarak, her zaman olmasa da, yeni bir kütüphane yaratmaya da yarıyor. Bu yan etkiden, fonksiyonumuzun isminde bahsetmemiz lazım.

<?php

class Library // Kütüphane
{
   // mainLibrary: Ana kütüphane
   private $mainLibrary = null;

   // getOrCreateMainLibrary: Ana kütüphaneyi al ya da yeni yarat
   public static function getOrCreateMainLibrary(): Library
   {
      if (self::mainLibrary === null) {
         self::mainLibrary = new Library();
      }

      return self::mainLibrary;
   }
}

Fonksiyonun ismini yukarıdaki gibi değiştirdiğimizde, artık yan etkilerden de haberdar olacağız. Bu fonksiyonu kullanan biri, eline geçecek olan ana kütüphanenin, eğer hâli hazırda tanımlı değilse baştan yaratılabileceğini bilecek. Bu da, çağırdığı fonksiyonla sadece bir okuma (CRUD) yapmadığını, aynı zamanda yazmaya (CRUD) sebep olabileceğini de bilecek.

CRUD nedir?

CRUD, veritabanı gibi kalıcı depolama (persistent storage) birimlerinde kullanılan dört ana işlemi anlatan bir kısaltmadır:

  • Create: Yazma
  • Read: Okuma
  • Update: Güncelleme
  • Delete: Silme

Uzun etki alanı için uzun isimler kullanın

Etki alanı (scope), iki süslü ayraç (curly brackets) arasında bulunan alanı isimlendirmek için kullanılır. Etki alanlarını baz alarak isimlendirme yapmak bize belli açılardan yarar sağlar: Örneğin, eğer etki alanı küçük bir fonksiyondan bahsediyorsak, kısa değişken isimleri kullanmak fonksiyonun anlaşılabilirliğini pek etkilemez. Öte yandan etki alanı ne kadar büyükse, değişken isimleri de aynı derecede detaylı olmalıdır.

Elbette genel olarak programlamadan bahsederken etki alanlarını genelde küçük tutmak anlaşılabilirlik açısından çok daha önemlidir. Etki alanı küçük fonksiyonlar, daha rahat anlaşılabilir fonksiyonlardır. Etki alanı küçük sınıflar (class), daha rahat değiştirilebilir sınıflardır. Bu konu üzerine daha detaylı bir yazı yazacağım.

Örnek

Bu arada, biz etki alanını baz alarak isimlendirme yapmaya geri dönelim. Aşağıda her ne kadar anlamsız değişken isimlerine sahip olsa da, etki alanı kısa olduğu için anlaşılması kolay bir örnek var:

<?php

class Book // Kitap
{
   // deletePages: Sayfaları sil
   // until: 'e kadar
   public function deletePages(int $until): void
   {
      for ($i = 1; $i < $until; $i++) {
         $this->deletePage($i);
      }
   }

   // deletePage: Sayfayı sil
   // page: Sayfa
   private function deletePage(int $page): void
   {
      $this->pages[$page] = null;
   }
}

Yukarıdaki örnekte, deletePages isimli fonksiyonda kullandığımız $i isimli bir değişken var. Bu değişkenin isminin herhangi bir anlamı yok. Fakat bu değişken, yalnızca for döngüsünün (loop) etki alanı içerisinde kullanılabiliyor. Bu etki alanı ise yalnızca 3 satır:

for ($i = 1; $i < $until; $i++) {
   $this->deletePage($i);
}

Değişkenin ne işe yaradığını sadece bakarak anlayabiliyoruz: Silinecek her sayfa için bir yineleme görevi görüyor. Yani, sayfaları tek tek gezmeye yarıyor. Şu durumda elbette ki $i değişkenini $currentPage yani şimdiki sayfa olarak da güncelleyebiliriz. Fakat etki alanı o kadar küçük ki, $i ismi de kodun anlaşılırlığını bozmuyor.

Şimdi de aşağıda etki alanı büyük olan bir örneğe göz atalım:

<?php

class Book // Kitap
{
   // addAuthorsToBooks: Kitaplara yazarları ekle.
   // authors: Yazarlar
   // books: Kitaplar
   public static function addAuthorsToBooks(array $authors, array $books): void
   {
      // all: Hepsi
      $all = 0;

      // Yazarlar dizisi boş mu?
      if (empty($authors)) {
         return;
      }

      foreach ($books as $book) {
         // updated: Güncellendi
         // false: Yanlış
         $updated = false;
         foreach ($authors as $author) {
            if (!$book->hasAuthor($author)) {
               $book->addAuthor($author);

               // true: Doğru
               $updated = true;
            }
         }

         if ($updated) {
            $all++;
         }
      }
   }

   // hasAuthor: Yazar ekli mi?
   private function hasAuthor(Author $author): bool;

   // addAuthor: Yazarı ekle
   private function addAuthor(Author $author): bool;
}

Temizlik zamanı

Pekala… Normalde bu kadar fazla derinlik içeren kod yazmıyorum. 🙂 Bana göre yukarıdaki kod birçok açıdan kötü ve kolay anlaşılabilir değil. Fakat, bir örnek verebilmek için etki alanı büyük bir kod yazmam gerekiyordu.

Gördüğünüz gibi, addAuthorsToBooks fonksiyonunun başında $all adında bir değişken var. İsmi gayet kısa. Fakat, yukarıdaki kodu baz alırsak, kullanılabilir olduğu etki alanı tam 27 satır. Yani oldukça kısa isimlendirilmiş bu fonksiyonun neler yaptığından tam olarak emin olmanız için 27 satırı incelememiz gerekiyor.

Haydi o zaman inceleyelim. 🙂

Değişkenimiz $all, bu 27 satırlık fonksiyonda iki noktada kullanılıyor: Birincisi, fonksiyonun en başında 0’a eşitleniyor. İkincisi ise, fonksiyonun sonlarına doğru, $updated adlı değişkenin true olduğu durumda değer artırımına uğruyor.

Sadece iki satırda kullanıldığı için çok da dert yaratmadı, değil mi? Yanlış. Çünkü şimdi de $updated değişkenini incelememiz lazım. O da aynı şekilde ilk olarak false‘a eşitleniyor, sonrasında eğer $book yani kitapta hâli hazırda $author yani yazar ekli değilse true olarak güncelleniyor.

Daha fazla derine inmeden, bu kodun verilen her yazar ve her kitap için çalıştırıldığını söylersek, takip etmenin ne kadar zorlaşacağını görmek de o kadar kolay olur sanırım. Bu değişkenin ne işe yaradığını anlamak için, kodun ne yaptığını çözmek gerekiyor. Şimdi, bu değişkenin ne yaptığını söylemeden önce, değişkeni, etki alanı büyük olduğu için, ismi daha uzun olacak şekilde güncelleyelim:

<?php

class Book // Kitap
{
   // addAuthorsToBooks: Kitaplara yazarları ekle.
   // authors: Yazarlar
   // books: Kitaplar
   public static function addAuthorsToBooks(array $authors, array $books): void
   {
      // updatedBookCount: Güncellenen kitap sayısı
      $updatedBookCount = 0;

      // Yazarlar dizisi boş mu?
      if (empty($authors)) {
         return;
      }

      foreach ($books as $book) {
         // updated: Güncellendi
         // false: Yanlış
         $updated = false;
         foreach ($authors as $author) {
            if (!$book->hasAuthor($author)) {
               $book->addAuthor($author);

               // true: Doğru
               $updated = true;
            }
         }

         if ($updated) {
            $updatedBookCount++;
         }
      }
   }

   // hasAuthor: Yazar ekli mi?
   private function hasAuthor(Author $author): bool;

   // addAuthor: Yazarı ekle
   private function addAuthor(Author $author): bool;
}

Eski ismi olan $all yerine $updatedBookCount ismini kullanmaya başladık. Şimdi, kodu incelemeden bu değişkenin neye yaradığını biliyoruz. Tüm o analizleri yapmamıza gerek kalmadı. Güncellenen her kitap için, değişkenin değeri bir artıyor.

İşte bu kadar basit. 🙂

Muğlak olmayan isimler kullanın

Yazılımda isimlendirmeye büyük önem vermemizin sebebi, isimlendirmenin doğru kullanıldığında bizi büyük zahmetten kurtarmasından kaynaklanıyor. Örneğin bir fonksiyonu doğru isimlendirdiğimiz zaman, o fonksiyonun ne yaptığını görmek için içindeki koda bakmamıza gerek kalmaz. Bu da bize hem zaman kazandırır, hem de mental kapasitemizi gereksizce harcamamızın önüne geçer.

Muğlak olmayan isimlerden önce, muğlaklık nedir onu konuşsak sanırım daha iyi olur.

Muğlak bir isimle karşılaştığımızda, bu ismin tam olarak neyi kastettiğini anlayamayız. Muğlak isim, birden fazla anlama geliyor olabilir. Ya da başka bir isimle büyük benzerlik gösterdiği için, o iki ismi birbirinden ayıramayabiliriz.

Örnek olarak “Şekerim var.” cümlesini düşünebiliriz. Sizce bu cümle ne anlam ifade ediyor? Cümlenin öznesi, bize şeker hastalığı olduğunu mu anlatmaya çalışıyor, yoksa evinde şeker bulundurduğunu mu? Bunu, cümlenin içinde bulunduğu yazıyı okumadan bilmemiz mümkün değil. Aynı şekilde kodlama yaparken de muğlak bir isimle karşılaştığımızda, ne anlama geldiğini öğrenmek için kodun içeriğini okumak zorunda kalıyoruz.

Muğlaklığı düşündüğümüzde, muğlak olmayan ismin belki de ayırıcı bir isim olması gerektiğini söyleyebiliriz.

Örnek

<?php

class Book // Kitap
{
   // markAsRead: Okunmuş say
   // page: Sayfa
   public function markAsRead(int $page): void
   {
      // ...
   }
}

Yukarıda çok basit bir örnek var. Book sınıfına ait tek fonksiyonumuz var. Bu markAsRead isimli fonksiyon, $page isminde bir argüman alıyor.

Peki sizce bu fonksiyon ne yapıyor? Ben size birkaç olasılık sayayım:

  1. Kitabı tamamen okunmuş sayabilir.
  2. Kitabın sayfa sayısını, verilen sayfa argümanıyla karşılaştırıp, eğer verilen sayfa sayısı yeterliyse okunmuş sayabilir.
  3. Kitabı, belirli bir sayfa sayısına kadar okunmuş sayabilir.
  4. Kitabın belirli bir sayfasını okunmuş sayabilir.

Daha fazla örnek dahi çıkarabilirim. Çünkü ne fonksiyonun ismi bize ayırıcı bir tanı sunuyor, ne de argümanın ismi bize yardımcı oluyor.

Temizlik zamanı

Fonksiyonumuz markAsRead‘in anlaşılır olmamasının temel sebebi, bir kitabın okunmuş sayılabilecek birden fazla elemanının olması. Kendisi, belli bir bölümü ya da sayfası gibi. Argümanımız $page‘in bize yardımcı olmamasının sebebi ise, bu sayfa sayısının ne olduğunu tam olarak bilemememizden kaynaklanıyor.

Peki diyelim ki böyle bir fonksiyonla karşılaştık, ne yaptığını anlamadık, biz de fonksiyonun içindeki kodu okumaya karar verdik. Diyelim ki, bu fonksiyon aslında kitabı, belirli bir sayfasına kadar okunmuş saymakla görevli. Nasıl bir fonksiyon imzasına sahip olmalıyız?

<?php

class Book // Kitap
{
   // markAsReadUntil: 'e kadar okunmuş say
   // currentPage: Şu anki sayfa
   public function markAsReadUntil(int $currentPage): void
   {
      // ...
   }
}

Fonksiyon ismini de, argüman ismini de biraz uzattık. Uzun isimler kullanmak da çok yararlı değil; fakat bu durumda bir gereklilik. Çünkü isimlerimizi uzatarak, fonksiyonun sadece imzasıyla ne yaptığını anlatabilmesini sağladık.

Üstelik, fonksiyon imzasını İngilizce olarak okuduğumuz zaman adeta bir cümle yaratmış olduğumuzu göreceğiz: Mark as read until current page. Fonksiyon ismi ile argüman isminin çevirisini de aynı şekilde yaptım: Şu anki sayfa ‘e kadar okunmuş say ya da düzeltilmiş hâliyle, şu anki sayfaya kadar okunmuş say.

Gördüğünüz gibi, artık fonksiyonumuzun ne yaptığı açık ve net ortada. Kim ki bu fonksiyonu kullanacak olsun, fonksiyonun içindeki kodu okumasına artık gerek kalmadı. 🙂

Mümkün olduğunca standart terimleri kullanın

İsimleri anlamayı kolaylaştırmanın bir yolu var: Hâli hazırda kullanımda olan veya bir standarda sahip isimleri kullanmak. Bu tarz isimlerin de kendi ismi var. 🙂 Biz bunlara terim diyoruz. Terimler, bir şekilde ya fazlaca kullanıldıklarından, ya da diğer isimler yerine kullanılmaları mantıklı olduğundan dile otururlar.

Standart terimleri kullanmak, kodumuzun başkaları tarafından daha rahat okunmasını sağlar. Kendi yarattığımız terimleri kullanmak yerine, var olanı kullandığımız zaman, başka yazılımcıların da içinde bulunduğu bir gruba dahil oluruz.

Kendi yarattığımız terimleri kullandığımızda, kodu gelecekte kolayca anlama ihtimali olan sadece kendimiz oluruz; ki bazı durumlarda bu terimleri unutacağımız için, belki biz bile kendi kodumuzu rahat okuyamayabiliriz. Öte yandan, herkesin kullandığı terimleri kullandığımızda, biz unutsak bile, yanımızda o terimleri unutmayacak koca bir topluluk buluruz.

Bu topluluk, zaman zaman küçük bir takım bile olabilir. Belli bir süre beraber çalışan yazılım takımları, kendi terim dağarcıklarını yaratabilir. Her ne kadar yazılım topluluğunun standartları olmasa da, kendi takımının standartlarına uymak da bireysel hareket etmekten daha yararlıdır.

Örnek

Aşağıdaki örnek, aslında doğru isimlendirmelere sahip bir kod parçasını içeriyor. İsimlendirmeler açık ve basitçe anlaşılabilir şekilde yapılmış. Fakat yine de, standart terimleri kullanarak kodu daha da kolay anlaşılır kılmak mümkün!

<?php

class Library // Kütüphane
{
   private $books = []; // Kitaplar
   
   // getFirstBook: İlk kitabı al
   public function getFirstBook(): Book
   {
      if ($this->count() === 0) {
         throw new BookNotFoundException('Kütüphanede kitap yok.');
      }

      // array_shift, bir dizideki ilk elemanı döndürür.
      return array_shift($this->books);
   }

   // getNumberOfBooks: Kitapların sayısını al
   public function getNumberOfBooks(): int
   {
      return sizeof($this->books);
   }
}

Kısaca anlatmak gerekirse, Library adında bir sınıfımız, bu sınıfın içerisinde $books adında, kitapları sakladığımız bir dizimiz (array), getFirstBook adında, bize kütüphanedeki ilk kitabı döndüren bir fonksiyonumuz, ve son olarak getNumberOfBooks adında, bize kütüphanedeki kitap sayısını veren bir fonksiyonumuz var.

Temizlik zamanı

Terimlere verilebilecek en iyi örnekleri, PHP’nin standart kütüphanesinde bulmak mümkün: SPL.

SPL’yi kullanarak örnek kodumuzda iki iyileştirme yapacağız. Bu iyileştirmeyi yapmaktaki ilk amacımız standart terimleri kullanmak; çünkü kodumuzu daha anlaşılır kılmak istiyoruz. Fakat bunun olumlu bir yan etkisi olacak ve SPL sayesinde, kodumuz aynı zamanda basitleşecek.

İlk olarak, getNumberOfBooks fonksiyonumuzu değiştireceğiz. SPL’de tanımlı Countable arayüzünü kullanacağız. Böylece, PHP dilinde kodlama yaparken, sayılabilir bir elemana sahip sınıfların uyguladığı standart arayüzü kullanmış olacağız. Bu arayüz hakkında daha fazla bilgiyi burada bulabilirsiniz.

<?php

class Library implements Countable // Sayılabilir Kütüphane
{
   private $books = []; // Kitaplar
   
   // getFirstBook: İlk kitabı al
   public function getFirstBook(): Book
   {
      if ($this->count() === 0) {
         throw new BookNotFoundException('Kütüphanede kitap yok.');
      }

      // array_shift, bir dizideki ilk elemanı döndürür.
      return array_shift($this->books);
   }

   // count: Say
   public function count(): int
   {
      return sizeof($this->books);
   }
}

Yaptığımız tek şey, sınıfımıza Countable arayüzünü uygulattırmak ve getNumberOfBooks fonksiyonunun adını count olarak güncellemek oldu. Böylece, PHP diline hakim birisinin, Library sınıfını gördüğü zaman sayılabilir olup olmadığını anlamak için $library->count() fonksiyonunu denemesi yeterli olacak.

Daha güzeli ise ne biliyor musunuz? PHP’nin çekirdeğinde yer alan bir arayüz kullandığımız için, PHP’nin çekirdeğinde yer alan count() fonksiyonunu da artık kullanabileceğiz. Yani count($library) şeklinde de sonuç almamız mümkün. Çünkü artık Library sınıfımız, sayılabilir bir sınıf.

İkinci değişimimizi, BookNotFoundException istisnası (exception) üzerinde yapacağız. Bu istisna sınıfını biz yaratmak zorunda kaldık. Eğer kütüphanede kitap yoksa, getFirstBook fonksiyonumuz bir kitap döndürmek yerine bu istisnayı tetikliyor. Halbuki SPL bize UnderflowException adında hazır bir istisna sınıfı sunuyor. Bu istisna, örneğin boş bir diziden eleman almaya çalışırken tetiklenmesi için yaratılmış. Bu istisna sınıfı hakkında daha fazla bilgiyi burada bulabilirsiniz.

<?php

class Library implements Countable // Sayılabilir Kütüphane
{
   private $books = []; // Kitaplar
   
   // getFirstBook: İlk kitabı al
   public function getFirstBook(): Book
   {
      if ($this->count() === 0) {
         throw new UnderflowException('Kütüphanede kitap yok.');
      }

      // array_shift, bir dizideki ilk elemanı döndürür.
      return array_shift($this->books);
   }

   // count: Say
   public function count(): int
   {
      return sizeof($this->books);
   }
}

Böylece, kütüphanede kitap yokken bir kitap almaya çalıştığımızda, PHP’nin standart istisna sınıflarından biri tetiklenmiş olacak. Bu da kodumuzun sadece kendimiz tarafından değil, PHP topluluğu tarafından rahatça anlaşılabilmesini sağladığımız anlamına geliyor.

Soyutlamanın seviyesine göre isimlendirme yapın

Soyutlamalar (abstraction), nesne yönelimli dillerin üç temel taşından biri. Diğer ikisi kalıtım (inheritance) ve kapsülleme (encapsulation) ama onlardan bu yazıda bahsetmeyeceğiz.

Soyutlama yaparken çoğu zaman nasıl bir somutlama (concretion) yapacağımızı da biliriz. Örneğin telefon için bir soyut sınıf (abstract class) ya da arayüz (interface) yazacaksak:

<?php

interface Phone // Telefon
{
}

Bu arayüzü oluştururken aklımızda nasıl somutlamalar kullanacağımız da vardır:

<?php

class HomePhone implements Phone // Ev telefonu
{
}

class OfficePhone implements Phone // Ofis telefonu
{
}

Bu nedenle arayüzü, yani soyut sınıfımızı oluştururken, sadece somut sınıflarımızda kullanmamız gereken isimleri kullanma hatasına düşebiliriz. Halbuki bu hata, soyutlamanın esnekliğinin kaybolmasına yol açar.

Örnek

Başladığımız örnekten devam edelim. Ev ve ofis telefonlarını düşünerek aşağıdaki gibi bir telefon arayüzü tasarlayalım:

<?php

interface Phone
{
   public function getNumber(): int; // Numarayı al
   public function cableConnected(): bool; // Kablo bağlı mı?
   public function hasDialTone(): bool; // Çevir sesi var mı?
}

Yukarıdaki arayüz, ev de ofis telefonlarını düşündüğümüzde oldukça mantıklı. İki somutlama da birer telefon numarasına sahip. İkisinin de çalışıp çalışmadığını anlamak için kablolarının bağlı olup olmadığını öğrenmemiz gerek. İkisinde de arama yapmadan önce çevir sesi olup olmadığını bilmemiz önemli.

Peki ya üçüncü bir somutlama işin içine girerse ne olacak? Mesela cep telefonu. Telefon numarası dışındaki fonksiyonlar cep telefonuna uygulanabilecek şeyler değil. Cep telefonunun çalışıp çalışmadığını anlamak için kablonun takılı olmasına değil, şarjına bakmamız gerek. Arama yapabildiğini görmek içinse, bir baz istasyonuna bağlı mı onu bilmemiz gerekiyor.

Temizlik zamanı

Cep telefonu somutlamasını düşündüğümüz zaman, telefon arayüzündeki isimlendirmelerin doğru olmadığını görüyoruz. Arayüzdeki isimlendirmeler, başka somutlamalardan izler taşıyor; yani gerçekte soyut isimler kullanmıyor.

Gördüğümüz gibi, bir soyutlama üzerinde soyut isim kullanmadığımız zaman, bu soyutlama esnekliğini kaybediyor. Daha katı terimlerle konuşacak olursak, cep telefonunu kodumuza dahil edemiyoruz.

Peki soyutlamamızı, yani örneğimizdeki arayüzü düzeltseydik ve soyut isimler kullansaydık nasıl görünürdü?

<?php

interface Phone
{
   public function getNumber(): int; // Numarayı al
   public function isOn(): bool; // Açık mı?
   public function hasLine(): bool; // Hat var mı?
}

Telefon arayüzünün fonksiyonlarından getNumber‘ı değiştirmemize gerek kalmadı; çünkü tüm telefonların bir numarası olmak zorunda.

Öte yandan, cableConnected adlı fonksiyonumuzun adını isOn şeklinde düzelttik. Kablo, aslen bir somutlamaya işaret ediyor. Yalnızca kablolu telefonların bir kablosu olur. Öte yandan, telefonun açık olup olmaması daha soyut bir kavram. Bir ev veya ofis telefonu kablosu bağlıyken açık olabilecekken, bir cep telefonu eğer şarjı varsa açıktır.

Son olarak da, hasDialTone adlı fonksiyonumuzu hasLine olarak güncelledik. Çevir sesi, yalnızca kablolu telefonlarda olan bir özellik. Cep telefonlarında çevir sesi aramadan, istediğimiz numarayı tuşlayarak arama yapabiliyoruz. Fakat cep telefonlarının arama yapabilmesi için bir baz istasyonuyla iletişim kurabiliyor olması gerekiyor. Tüm telefonları düşünerek bir soyut kavram çıkaracak olursak, bunu telefonun hat alıp alamamasına bağlayabiliriz.

Telefon arayüzümüz şimdi gerçekten soyut bir hâle kavuştu. Sadece cep telefonu değil, uydu telefonu ya da aklınıza gelebilecek herhangi başka bir tip telefonu da bu arayüzü kullanarak kodlamamız artık mümkün.

Açıklayıcı isimler kullanın

İsimlendirme, programlamanın belki de en önemli adımıdır. Elbette değişkenlere, fonksiyonlara ya da sınıflara ne isim koyacağınız, kodunuzun nasıl çalışacağını etkilemez. Sonuçta kodunuz bir derleyici (compiler) ya da bir yorumlayıcı (interpreter) tarafından bilgisayarın anlayabileceği bir dile çevrilir. Fakat kodunuzda kullandığınız doğru isimlendirme teknikleri, kodunuzu insanlar tarafından anlaşılabilir hâle getirir. Göreceksiniz ki, bir kod parçasını anlaşılabilir kılmak, bilgisayarın kodu çalıştırabilmesinden çok daha önemlidir.

Kodun anlaşılır olması, onu aynı zamanda esnek hâle getirir. Esnek kod, değişmesi kolay kod demektir. Yazdığınız kod, son kullanıcının ihtiyaçlarını karşılamaya yönelik çalışmaya ne kadar yakınsa, o kadar esnek olmalıdır; çünkü son kullanıcının ihtiyaçları değişime fazlasıyla açıktır. Burda “son kullanıcının ihtiyaçları” olarak tanımladığım meta İngilizce’de “business logic” ya da “domain logic” olarak geçiyor. Bu konu üzerine detaylıca eğilen farklı bir yazı yazacağım.

Örnek

Kendinize biraz zaman tanıyın ve aşağıdaki örnekte bulunan kod parçasının ne işe yaradığını çözmeye çalışın. Bakalım çözebilecek misiniz? 🙂

<?php

public function c(int $t): void
{
   $cf = -999;
   $e = null;
   for ($i = 0; $i < count($l); $i++) {
      $ef = $l[$i]->f;
      if (abs($ef - $t) < abs($cf - $t)) {
         $cf = $ef;
         $e = $l[$i];
      }
   }

   $e->f = $t;
}

Temizlik zamanı

<?php

class ElevatorController // Asansör Kontrolü
{
   // callElevator: Asansör çağır
   // currentFloor: Bulunulan kat
   public function callElevator(int $currentFloor): void
   {
      // availableElevator: Müsait asansör
      // closestElevator: En yakın asansör
      $availableElevator = $this->getClosestElevator($currentFloor);

      // moveTo: Taşı
      $availableElevator->moveTo($currentFloor);
   }
}

class Elevator // Asansör
{
   private $elevators;

   // floor: Kat
   public function getClosestElevator(int $floor): Elevator
   {
      // allElevators: Tüm asansörler
      $allElevators = clone $this->elevators;

      do {
         // nextElevator: Sıradaki asansör
         $nextElevator = pop($allElevators);

         // currentElevatorDistance: Şu anki asansörün uzaklığı
         $currentElevatorDistance = abs($closestElevator->floor - $floor);

         // nextElevatorDistance: Sıradaki asansörün uzaklığı
         $nextElevatorDistance = abs($nextElevator->floor - $floor);

         // Eğer sıradaki asansör daha yakınsa...
         if ($nextElevatorDistance < $currentElevatorDistance) {
            // En yakın asansör olarak, sıradaki asansörü ata.
            $closestElevator = $nextElevator;
         }

      // Kontrol edecek asansör kaldıysa, kodu tekrar et
      } while (!empty($allElevators));
      return $closestElevator;
   }

   public function moveTo(int $floor): void
   {
      // Asansörün bulunacağı katı belirle
      $this->floor = $floor;
   }
}

Yukarıdaki kod, ilk paylaştığım çirkin kodla aynı işi yapıyor. Hatta fark ettiyseniz, yeni kod çok daha uzun: İki adet yeni sınıf, toplamda da üç adet yeni fonksiyon yarattık. Türkçe karşılıklarının yazıldığı yorumları saymasak bile, kodumuz yine de uzadı.

Öte yandan, yeni yazdığımız kod parçası çok daha rahat okunabiliyor. Sadece sınıf ve fonksiyon isimlerini okuyarak kodun ne yaptığını anlayabiliyoruz.

ElevatorController adlı sınıf asansörleri kontrol etmeye yarıyor. Bu sınıfın içindeki callElevator fonksiyonu, müsait bir asansörü bulunduğumuz kata çağırmamızı sağlıyor.

Elevator sınıfı içerisindeki getClosestElevator fonksiyonu, belirttiğimiz kata en yakın olan Elevator yani asansör elemanını sunuyor. moveTo fonksiyonu ise, bir Elevator elemanını istediğimiz kata taşımamızı sağlıyor.

İngilizce bilmeseniz dahi, yazdığım Türkçe karşılıkları ile beraber, kodun ne yaptığını anlamanız inanılmaz basitleşti. Bu da, asansörlerin çalışma mantığında bir değişikliğe gitmek istediğimizde, kodun buna izin verecek kadar esnek olmasını sağladı.

Kodu yorum içerisine alarak devreden çıkarmayın

Bir kod parçasının çalışmasını artık istemiyorsanız, en iyi yöntem onu silmektir. Bazı yazılımcılar, artık çalışmasını istemedikleri bu kod parçasını aşağıdaki şekilde yorum içerisine alıyorlar. Çoğu zaman bunun sebebi, o kod parçasını kaybetmekten korkmak oluyor. Halbuki bir versiyon kontrol sistemi (örneğin git) kullanıyorsanız, o kod parçasına her zaman ulaşabilirsiniz.

Versiyon kontrol sistemleriyle bir kodu geri getirebilme olanağımız dışında, bir kodu yorum içerisine almamak için kendi tecrübelerimden verebileceğim örnekler de var. Kariyerim boyunca ne zaman bir kodu devre dışı bırakmam gerekse, belki de bu durumların %99’unda o koda bir daha ihtiyaç duymadım. Ya daha iyi bir kod parçası yazdığım için eskiyi devre dışı bıraktım ya da artık o özelliğe ihtiyaç duymadım.

Yazılım, her gün gelişen bir alan. Kodunuzu silmekten korkmayın. Hatta ve hatta, bir proje içerisinde başka yazılımcılarla birlikte çalışıyorsanız, başkasının yorum içerisine aldığı kodu silmekten de korkmayın. Böyle yorumlar bize yarardan çok zarar getirir.

Örnek

<?php

class Book // Kitap
{
   // getTitle: Başlığı al
   public function getTitle(): string
   {
      //return DB::table('books')->where('id', $this->id)->first()->title;
      return $this->title;
   }
}

Yorum içerisine alınmış kodu fark ettiniz mi? Belli ki, kitap başlığını almak için daha önceden direkt olarak veritabanı bağlantısı kuruluyormuş. Fakat sonradan bu fonksiyon Book sınıfı (class) içerisine alınmış ve başlığa direkt olarak $this->title şeklinde bir ORM kullanılarak ulaşılabiliyor.

Temizlik zamanı

Yorum içerisine alınmış koda ihtiyacımız yok. Üstelik kitap başlığını almak için yöntemimizi bile değiştirmişiz; üstteki kodu kullanmanın hiçbir mantığı kalmamış.

Belki de yazılımcı, bir ORM kullanmadan kitap başlığına nasıl ulaşacağını hatırlamak istediği için bu kodu yorum içerisine almış. Fakat bu artık Book sınıfıyla ilgili bir kod parçası değil. Belli ki artık projeyle dahi alakası kalmamış.

<?php

class Book // Kitap
{
   // getTitle: Başlığı al
   public function getTitle(): string
   {
      return $this->title;
   }
}

Alıntı

Bu konu hakkında Robert Martin çok güzel yazmış:

Yorum içerisine alınmış kod parçaları görmek beni çılgına çeviriyor. Ne kadar eski olduğunu ya da bir anlam ifade edip etmediğini kim bilir? Yine de kimse bu kod parçasını silmiyor çünkü herkes bir başkasının bu bilgiye ihtiyacı olacağını ya da birilerinin bununla ilgili bir planı olabileceğini varsayıyor.

Oysa o kod parçası sessizce orda yaşamaya devam ediyor; zaman geçtikçe çürümeye başlıyor ve günbegün kodun geri kalanıyla olan ilişkisi azalıyor. Çağırdığı fonksiyonlar artık yok. Kullandığı değişkenlerin çoktan adı değiştirilmiş. Yazıldığı standartlar artık kullanılmıyor. İçinde bulunduğu modülleri kirletmekten ve onu okuyan insanların dikkatini dağıtmaktan başka bir işe yaramıyor. Yorum içerisine alınmış kod parçası, rezillikten başka bir şey değil.

Clean Code, Robert C. Martin

Sonuç: Eskimiş kodunuzu yorum içerisine almayın. Silin gitsin!

Yorumun kalitesi, kodun kalitesi kadar önemlidir

Yazılımcılar olarak ürettiklerimizle gurur duymak bizim için bu işin getirdiği ek faydalardan biri. Ne de olsa hiç yoktan yeni bir şey yaratıyoruz ve bu yarattığımız şeyin çalıştığına tanık oluyoruz. Bundan daha keyifli ne olabilir ki?

Ürettiklerimize ek olarak, nasıl ürettiğimizin de önemi var. Hele ki yazdığımız kodun oldukça şık ve zarif olduğunu düşünüyorsak, yarattığımız somut şey her ne ise, onu çalıştıran koddan da aynı şekilde gurur duyuyoruz. Bu nedenle kod parçalarında yazar adı görmeye oldukça alışkınız:

<?php

// Author, Yazar: Ekin Öcalan
// Date, Tarih: Mar 9, 2019

Çoğu yazılımcı, ki bu gruba geçmişte ben de dahildim, bu gurur hissiyatını yorumlara da taşımaktan çekinmez. Şık ve zarif bir kod parçasının kendi kendini anlatabilmesini bekleriz; halbuki biz yazılımcılar, yazdığımız kod parçasının ne denli akıllıca olduğunu başkalarına da göstermek için kendimizi o yorumu yazmaktan alıkoyamayız. Bunu da havalı bir şekilde yapmaya bayılırız. 🙂

Fakat gerçek şu ki, kod parçaları bizim egomuzu yansıtacak mantıklı birer meta değil. Eğer kodumuz kendini yeterince açık anlatamıyorsa, yorum yazmak yerine yazdığımız kodu düzeltmek ilk deneyeceğimiz şey olmalı. Eğer başarısız olursak, yorumlar elbette ki yardımımıza koşacaktır. Fakat burda önemli olan şey egomuzu yansıtmak değil, yorumun okuyucular tarafından kolayca anlaşılabilmesini sağlamak.

Eğer bir yorum yazacaksak, zaman ayırıp en iyi şekilde yazmaya özen göstermeliyiz.

Örnek

Konu yorum olunca verilebilecek çok fazla örnek oluyor. Gelin birkaçına beraber bakalım:

<?php

abstract class Book // Kitap
{
   /**
    * Kitaplardan oluşan bir koleksiyon alır,
    * bu kitapları tek tek veritabanına ekler.
    * Eklediği her kitap, veritabanında listenin
    * sonuna eklenir.
    */
   public static function pushAll(array $books): void;

   /**
    * Listenin sonuna bir kitap ekler.
    */
   public static function push(Book $book): void;

   /**
    * Sondaki kitabı döndürür. Fakat bunu yapabilmesi için
    * önce push() metoduyla bir kitap eklemeniz gerekir.
    * Evet, sana diyorum. Kitap eklemeden kitap almayı
    * beklemiyordun değil mi? :-) Hadi sana bir de kolaylık
    * yaptım, pushAll() diye bir fonksiyon ekledim. Koyacak
    * birden fazla kitabın varsa tek tek uğraşmazsın. ;-)
    */
   public static function pop(): Book;
}

Temizlik zamanı

Fonksiyonlarımızın tek tek üzerinden geçelim:

/**
 * Kitaplardan oluşan bir koleksiyon alır,
 * bu kitapları tek tek veritabanına ekler.
 * Eklediği her kitap, veritabanında listenin
 * sonuna eklenir.
 */
public static function pushAll(array $books): void;

İlk hata ilk cümlede karşımıza çıkıyor. array, Türkçe’ye dizi olarak çevrilmiş bir veri yapısıdır. Koleksiyon ise bambaşka bir şey. Yorumlarda doğru terimleri kullanmalıyız.

/**
 * Kitaplardan oluşan bir dizi alır,
 * bu kitapları tek tek veritabanına ekler.
 * Eklediği her kitap, veritabanında listenin
 * sonuna eklenir.
 */
public static function pushAll(array $books): void;

İkinci hata, yorumlarda veritabanından bahsetmek. Fonksiyonun içeriğini bilmiyoruz; dolayısıyla fonksiyon veritabanı kullanıyor mu onu da bilmiyoruz. Veritabanı, bir veri depolama yöntemidir ve alternatifleri vardır. İlerde veritabanını farklı bir veri depolama yöntemiyle değiştirmemiz olasılıklar dahilinde. Bunu yaparken yorumu güncellemeyi unutmamız da oldukça olası. O yüzden bu detayı yorumlara yazmak mantıklı değil. Yorumlarda uygulama detaylarından bahsetmek kod/yorum tutarsızlığına bir davetiyedir.

/**
 * Kitaplardan oluşan bir dizi alır,
 * bu kitapları tek tek ekler.
 * Eklediği her kitap, listenin
 * sonuna eklenir.
 */
public static function pushAll(array $books): void;

Üçüncü hata, apaçık ortada olanı tekrar etmek. $books argümanını tanımlarken, bunun bir array olduğunu zaten belirttik. Yorumlarda zaten açıkça belli olan bir şeyi tekrar etmeye gerek yok.

/**
 * Kitapları tek tek ekler.
 * Eklediği her kitap, listenin
 * sonuna eklenir.
 */
public static function pushAll(array $books): void;

Dördündü hata, yorumun gerekenden fazla uzun olması. Evet, yorumun aslında kısa göründüğünün farkındayım; fakat çok daha kısa olabilir. Yorum ne kadar kısaysa, onu güncel tutmak da o kadar kolay olur.

/**
 * Kitapları listenin sonuna ekler.
 */
public static function pushAll(array $books): void;

Çok güzel. Yorumumuzu tek satıra kadar düşürdük. Şimdi, bir sonraki fonksiyonumuza bakalım:

/**
 * Listenin sonuna bir kitap ekler.
 */
public static function push(Book $book): void;

Fark ettiniz mi? 🙂 push() fonksiyonunda kitabın listenin sona ekleneceği zaten yazılmış. pushAll() bu fonksiyonun bir varyasyonu olduğu için, bu bilgiyi tekrar etmemize gerek yok. Tek yapmamız gereken, push() fonksiyonuna gönderme yapmak:

/**
 * @see push()
 */
public static function pushAll(array $books): void;

Öte yandan, aslında push() fonksiyonunun da yoruma ihtiyacı yok. Çünkü push terimi yazılımcılar tarafından bilinen bir terim ve bu terim zaten bir elemanı listenin sonuna eklemesiyle bilinir. Ama biz örneğimiz gereği bu yorumu yerinde bırakıp sonraki fonksiyonumuza geçelim:

/**
 * Sondaki kitabı döndürür. Fakat bunu yapabilmesi için
 * önce push() metoduyla bir kitap eklemeniz gerekir.
 * Evet, sana diyorum. Kitap eklemeden kitap almayı
 * beklemiyordun değil mi? :-) Hadi sana bir de kolaylık
 * yaptım, pushAll() diye bir fonksiyon ekledim. Koyacak
 * birden fazla kitabın varsa tek tek uğraşmazsın. ;-)
 */
public static function pop(): Book;

pop terimi de aynı push gibi bilinen bir terim. Listenin sonundaki elemanı döndürür ve bu elemanı listeden siler. Fakat bu fonksiyonun yorumundaki tek ve büyük hatayı görebiliyor musunuz? 6 satır yorum yazmışım ama ilk cümle dışında bana yararı olabilecek hiçbir şey eklememişim. Abuk sabuk yorumlar yazmak, okuyucuyu asıl odaklanması gerekenden uzaklaştırır.

/**
 * Sondaki kitabı döndürür.
 */
public static function pop(): Book;

Şimdi sınıfımızın (class) son hâline bakalım ve düzelttiğimiz hataları tekrar sıralayalım:

<?php

abstract class Book // Kitap
{
   /**
    * @see push()
    */
   public static function pushAll(array $books): void;

   /**
    * Sona bir kitap ekler.
    */
   public static function push(Book $book): void;

   /**
    * Sondaki kitabı döndürür.
    */
   public static function pop(): Book;
}

Sınıfımızın oldukça kısaldı; böylece hem daha anlaşılabilir hâle geldi, hem de olası güncellemelerimiz kolaylaştı.

Hataları düzeltirken kullandığımız bilgiler:

  1. Yorumlarda doğru terimleri kullanmalıyız.
  2. Yorumlarda uygulama detaylarından bahsetmek kod/yorum tutarsızlığına bir davetiyedir.
  3. Yorumlarda zaten açıkça belli olan bir şeyi tekrar etmeye gerek yok.
  4. Yorum ne kadar kısaysa, onu güncel tutmak da o kadar kolay olur.
  5. Abuk sabuk yorumlar yazmak, okuyucuyu asıl odaklanması gerekenden uzaklaştırır.