Kotlin ile null dan Kaçmak

Merhaba sevgili okurlarım,

En son yazı yazalı kaç ay olmuş. Kendimle alakalı gelişmelerden ve güncellemelerden haber vermem gerekirse (ki gerekir) Peakup isimli tatlı mı tatlı güzel mi güzel bir firmada çalışıyorum artık(Yaayyy) 3. ayım birkaç gün önce dolmuş bulunmakta efendim. Tek Android Developer'ım bu 3 ayda Google Play Store'a 3 adet Android Uygulama çıkardık (Yaayyy) 4. uygulama yolda. 5 6 7 diye seriye bağlamayı düşünüyorum. Bağımlılık yaptı Store'a ayda 1 yeni uygulama koymazsam içim rahat etmiyor valla o derece. Envisense ve Hodoor'dan bahsetmek istiyorum kısaca. Envisense şirketimizin ışıklarını ve havalandırmasını açıp kapatabildiğimiz güzel basit bir uygulama. Kışın daha otobüsteyken havalandırmayı açıyoruz ve biz geldiğimizde çoktaan ısınmış oluyor. Işık veya klima açıp kapatmak için yerinizden kalkıp bir yere gitmeniz gerekmiyor. Hatta son zamanlarda bu uygulamaya ufak bi ses kontrolü de ekledim. "Sahne'yi aç" demeniz Sahne bölgesindeki ışıkların açılması için yeterli oluyor. Tam developerlar için yazılmış bir uygulama (hiç konuşmayın tembelsiniz olm. Neyse tamam konuyu değiştiriyorum) Hodoor ise bir Access System. Yani size özgü kartınız ile giriş çıkışlarınızı yöneten bir sistem. Ona yaptığımız Android uygulamanın UI özellikleri ve renkleri o kadar tatlı ki görseniz ağzını yersiniz valla. Bu 2 uygulama da 0dan benim başlattığım projeler olduğu için tabiiki Kotlin ile yazıyorum. Ne demişler Denize dalmadan yüzme öğrenilmez. Hem yazdıkça aşinalık ve derinlik kazanıyorsunuz. Bu yazıdan yeterince faydalanabilmeniz için Kotlin hakkında biraz bilginizin olması gerekmektedir. Onu da söylemiş olayım. Gelelim Kotlin'e 


Bu yazıda sizlere Kotlin'deki Null-Safety özelliğinden derince bahsedeceğim. Hep söyleyip duyup duruyoruz null-safety diye. Peki tam olarak nedir bu? Kotlin'de null olamıyor mu değişkenler diye kendime sorup duruyordum kaç zamandır. Ama işin rengi Kotlin ile en az orta ölçekli projeler yazmadan belli olmuyor. Dedim ya denize dalmadan yüzme öğrenilmez. Yazıda bahsedeceğim özellikleri 3 ana başlık altında toplayabilirim. 


Nullable değişkenler.
Null Pointer Exception lar.
Objeleri NPE ye yol açmadan nasıl çağırabiliriz.


Eğer bir Java geliştiricisiyseniz veya Java kullanarak herhangi birşey yaptıysanız NullPointerExceptionların azizliğini mutlaka yaşamışsınızdır. Java'da primitive tipler haricindeki tüm değişkenler memory adreslerinin referanslarını tutarlar. Referans oldukları için onları nulllayabilirsiniz. Sistem valid bir referans beklerken o null ise NPE sizi karşılar. Exception Handling yapmadıysanız uygulama durdurulur(Crash) Ancak Kotlin baştan daha güvenli bir dil olmaya çalışmıştır. Default olarak değişkenler zaten nullanamaz referanslardır ve nulllanamazlar.


Değişkenleri sonlarına ? ekleyerek nullanabilir yapabiliriz. Ancak bunu yaparsak bu sefer o değişkenin her kullanımında derleyici derleme zamanında onun güvenli bir şekilde çağırılıp çağırılmadığını kontrol edecektir. Her ne kadar Kotlin NPE'yi bitirmeye çalışsa da ne yazıki "NPE sonsuza kadar gitti" diyemiyoruz. Gerçekten gerekliliği çok yüksek olan değişkenleri nullanabilir yapmalıyız yani. Her değişkenin sonuna koy soru işaretini takıl. Öyle olmaz paşam öyle olmaz anam babam. Bu konuda Best Practice kesinlikle nullanabilir değişken yazmamaktır. Ancak zorunda kaldıysanız neler olur? Yazının devamında enine boyuna bunu irdeleyeceğiz. 


1. Null Check Yazmak


NPE nin ne olduğunu anladıysak değişkenlerin null olup olmadığını kullanmadan önce kontrol edebiliriz tabiiki. Bu Defensive Programming oluyor. Defensive programming bazı değerler null olsa bile uygulamanın çalıştığından emin olmaktır. Bu özellikle bilgi güvenliği çok önemli olan uygulamalarda daha fazla tercih edilen bir yöntemdir. 
Null check henüz initialize etmediğiniz durumlar için veya tek bir kere çağıracağınız (Singleton) değişkenler için daha mantıklı bir yöntemdir.


2. Safe Call operatörünü kullanmak

Nullcheck in ne kadar güzel bir şey olduğunu gördük. Ki biraz programlama yaptıysanız bunu çoktan öğrenmişsinizdir. Ancak kod bu sefer çok temiz olmayan takibi ve yönetimi zor olan şöyle bir yapıya dönüşmeye başlıyor: 

if (loggedInUser != null) {
     if (userToSend != null) {
          if (validator != null) {
               validator.canUserSendMessage(loggedInUser, userToSend)
           } //end if (validator != null)
      } //end if (userToSend != null)
} //end if (loggedInUser != null)


Kotlin bu tip çağrıları sevmiyor lanetliyor itinayla esefle kınıyor. Kotlin'de değişken tanımlarkenki gibi sadece bi soru işareti bu kontrolü zaten yapıyor.

val switch = arguments?.getString(EXTRA_SWITCH)

Yukarıdaki satırda eğer arguments null ise get(string) methodu asla çalışmayacaktır. switch ise null bir değişken olarak kalacaktır. Kodun ne kadar kısa ve temiz olduğuna bakar mısınız. if not null check leri ile kıyaslanamayacak derecede güzel bir çözüm. Ayrıca zincirleme bir kontrol yapmak da mümkün. Alttaki kontrol yine herhangi bir değişken null olduğunda null değeri dönecektir.

var isUserVerified = object?.first?.second?.third?.active()

 

3. Let Kullanımı

Let kullanımını uzun uzun bir başka yazımda anlatmıştım zaten. O yüzden bu yazıda çok ayrıntıya girmeyeceğim. Kısaca açıklamam gerekirse (ki gerekir) Let, sadece kendisinden önceki değişken null değilken çalışan bir bloktur. Bu bloğun içinde it isimli bir değişken vardır. Ve nullanamaz. Let'ten önceki değişken let bloğu içinde nullansa bile it nullanamaz. Tabiiki it i tekrar isimlendirebilirsiniz. Hemen bir örnek üzerinden açıklayalım: 

arguments?.getString(EXTRA_FILE_PATH)?.let {
     val file = File.fromPath(it)
     fileUpdateBroadcastReceiver = FileUpdateBroadcastReceiver(it) {
           refreshView()
     }
}

Yukarıdaki satırlarda let bloğu sadece ama sadece arguments null değilse ve EXTRA_FILE_PATH parametresini barındırıyorsa çalışır. Aksi takdirde o bloğa uğranılmaz. Ve yukarıda da bahsettiğim gibi it değişkenini değiştiremezsiniz ve nullayamazsınız. Alttaki birinci örnekte kod derlenmez. İkinci örnekte ise konsolda Enjoy yazısını görürsünüz. str değişkenini değiştirseniz bile it değişkenine etkiniz olmaz olamaz.

var str: String? = null
str?.let { 
    it = "fdsa" // compile time error Val cannot be reassigned
    it = null // compile time error Val cannot be reassigned
}

 

var str: String? = null
    str = "asdf"
    str?.let {
        str = "fdsa" // no error
        print(if (it == str) "Kill yourself" else "Enjoy")
    }

Küçük bir not belirtmekte fayda var. İç içe let bloğu oluşturmanız gereken durumlar oluşabilir. Böyle durumlarda it değişkeninin adını değiştirmenizde fayda olacaktır. Bunu aşağıdaki örnekte görebilirsiniz.

str?.let { newVariableInsteadOfIt ->
    print(if (newVariableInsteadOfIt == str) "Kill yourself" else "Enjoy")
}

 

4. Güvenli Castlar

Tabiiki güvenilir film oyuncularını kastetmiyorum. Eva Green'e ver 200K $ koy cadı filmine taş gibi oynasın tek başına sırtlasın. Yok öyle, öyle yok öyle, o yok o. ClassCastException ile başa çıkmaktan bahsediyorum. Kotlin'de tabiiki bunun için çok güzel çözümLER var. 

onItemClickListener = context as? OnItemClickListener

Yukarıdaki satırda onItemClickListener sadece context OnItemClickListener ise başarıya ulaşır. Yani eğer Activity sınıfınız OnItemClickListener'ı implement ediyorsa o satır çalışır.
Ayrıca alttaki örnekte let bloğu içerisine girilirse zaten cast çoktan başarıya ulaşmış ve değişken kullanıma hazırdır. İntent yapısında oldukça kullandığım bir koddur kendileri

(intent?.extras?.getString(MainActivity.EXTRA_REPAST) as String).let { repast ->
    getMealsByRepast(repast)
}

 

5. Eşitlik ifadesi

Her Object Oriented dilde olduğu gibi Kotlin'de de tabiiki == operatörü var. Bu arkadaş önce solundaki değişkeni kontrol ediyor. Eğer null ise equals methodunu çağırmıyor bile. Eğer soldaki değişken null değilse, sağındaki değişkene bakıyor. Eğer o null ise yine equals methodu çağırılmıyor. Yani == operatörü kendi içinde nullcheck e zaten sahip. equals methodlarını güvenle == e dönüştürebilirsiniz.

 

6. elvis operatörü

?: yapısıdır. != null gibi bir anlama sahiptir. Kod içerisinde sık sık alttaki satırı göreceksiniz. Kendiniz de defalarca yazmışsınızdır :)

if (mealList != null) return mealList.size else return 0

İşte bu satır Kotlin ile şuna dönüşüyor.

mealList?.size ?: 0

Ben genel olarak Adapter sınıflarında listeyi nullable yapmam. En kötü boş liste yollarım. Böylece bu satır benim için sadece mealList.size a dönüşür.

 

7. Design by Contract

Bu kavram bir methodun input ve outputlarını kontrol eden yöneten bir kavramdır. Bir method Design by Contract prensibine göre List<String> dönecekse bundan başka birşey dönmemelidir. null da dönemez tabiiki. Aşağıdaki örnekte return tipini optional yaparsanız gidip bu methodun her kullanıldığı yere safe call ifadesi(!!) eklemeniz gerekmektedir. Ayrıca takım halinde çalışıyorsanız ve bu methodu pek çok iş arkadaşınız kullanıyorsa ayıkla pirincin taşını hadi bakalım uğraş dur... Open for extension close for modification diye boş yere demiyoruz heralde.

val file = File(path)
val list = file.listFiles()
    ?.filter { showHiddenFiles || !it.name.startsWith(".") }
    ?.filter { !onlyFolders || it.isDirectory }
    ?.toList()
return list ?: listOf()

Methodunuzu en kötü boş liste olarak döndürürseniz daha güzel olur. Ayrıca diğer iş arkadaşlarınız zaten o methodun boş liste dönme ihtimalini çoktan düşünmüşlerdir. Less change more yield

Aklınızda bulunsun diye birkaç küçük ayrıntıdan daha bahsetmek istiyorum:

  •  Açık net tutarlı class ve interfaceler kullanın. 
  •  Diğer geliştiricilerin yazdığınız sınıfı nasıl kullanacağı konusunda tahminler yapmayın. Constructorlarda null yollayacaksanız ve bunun aracılığı ile başka initialize işlemleri yapacaksanız dikkatli olun. 
  •  Nullable operasyonları tek bir method veya sınıfa alabiliyorsanız alın. Böylece kodunuza ? veya !! veya ::isInitialized saçmak zorunda kalmazsınız. 

 

8. Assert Not-Null

!! operatörü, Android Studio'yu değişkenin null olmadığına zorla ikna etmek için kullanılır. Resmen Android Studio'ya köpek çekmiş oluyorsunuz "sen sus sen kim köpek null olup olmadığını bileceksin ben null değil diyorsam null değil uleeyynnn" demiş oluyorsunuz. Riskli mi evet. Hem de ÇOOK. Değişken null iken !! ifadesi kullanmak buz gibi bir NPE duşu yaşatacaktır. Bunun yerine ? ifadesi kullanmak daha iyi olur. Eğer !! ifadesini kullanmak zorundaysanız şunlara dikkat etmeniz gerekmektedir:

  •  Kodunuzu kontrol edin. Akışları güzelce irdeleyin. Birisi gelip değiştirdiğinde bu ifadenin olduğu yerler patır patır NPE verebilir. NPE'ye yol açmayacak durumlarda kullanın.
  •  Dış güçlere gözünüzü kapatıp güvenmeyin. Yani dışarıdan gelen dataya. Kaçırdığınız bir case olabilir. Test yaparak bunlara engel olun.
  •  Her değişkenin kendi görevi olsun. Böylece değişimlerden etkilenen onlarca kod parçası olmaz. (Single Responsibility)

 

9. Değişken initialize etmeyi erteleme

Heryerde söylediğim ve söyleyeceğim çok sevdiğim lateinit özelliği. Android yaşamdöngüsünde bazen önce değişkeni declare etmeye ihtiyacınız vardır. Ama initialize etmeyi hemen yapmaya ihtiyacınız yoktur. Böyle bir durumda lateinit özelliğini kullanırız. Hemen örnekleyeyim. Genelde adapter sınıfı objeleri Global değişkenler olmak zorundadır. Farklı yerlerde intialize etmek ve update etmek için kullanılırlar. Böylece !! operatörüne de ihtiyacınız kalmaz. 

private lateinit var mealAdapter: MealAdapter

lateinit değişkenlerini kullandığınız yerde daha önce tanımlamış olduğunuzdan emin olmanız gerekmektedir. Yoksa IllegalStateException ile karşılaşırsınız. Dikkatli kullanmakta fayda var.
Yazıyı okuduğunuz için teşekkür ederim. Bana ulaşmak için alp.develioglu@gmail.com adresini kullanabilirsiniz.

Sevgiler :)

Blog yazılarına abone olmak ister misiniz ?

Her yeni blog yazısı çıktığında veya soru yayınlandığında bildirim almak için e-mail adresinizi yazmanız yeterli.