“Effective Java (3rd Edition)” kitabından en iyi uygulamalar
Joshua Bloch’un “Effective Java (3rd Edition) ” kitabından alıntılar
1. Null yerine boş collection veya dizi döndürün
Metot bir Set döndürüyorsa, boş bir sonuç için return null; yazmayın – bunun yerine return Set.of(); kullanın.
2. Constructor yerine static factory metotlarını değerlendirin
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
Her zaman daha iyi değildir, ama bazı kullanım senaryolarında pek çok avantaj sunar:
- Static factory metotlarının bir avantajı, constructor’ların aksine isimlerinin olmasıdır.
- Her çağrıldıklarında yeni bir nesne oluşturmak zorunda değildirler.
- Dönüş tiplerinin herhangi bir alt tipinden nesne döndürebilirler.
- Döndürülen nesnenin sınıfı, girdi parametrelerine bağlı olarak çağrıdan çağrıya değişebilir.
- Metodu içeren sınıf yazıldığında, döndürülen nesnenin sınıfının henüz var olması gerekmez.
Dezavantajları:
- Yalnızca static factory metotları sunmanın başlıca kısıtı, public veya protected constructor’ı olmayan sınıfların alt sınıflarının oluşturulamamasıdır.
- Static factory metotlarının ikinci zayıf noktası, programcıların bunları bulmakta zorlanmasıdır.
3. Çok sayıda constructor parametresiyle karşılaştığınızda bir builder düşünün
Static factory’ler ve constructor’lar ortak bir kısıtı paylaşır: Çok sayıda isteğe bağlı parametreye iyi ölçeklenmezler. Bu sorun, üzerinde çalıştığım bazı projeleri çok sert vurdu.
4. Singleton özelliğini private constructor veya enum tipiyle zorunlu kılın
Birden fazla örneğe ihtiyaç duymayan her şey yalnızca bir kez örneklenmelidir – ister @ApplicationScoped ile, ister başka bir singleton deseniyle.
5. Örneklenemezliği private constructor ile zorunlu kılın
Zaman zaman yalnızca static metotlar ve static alanlardan oluşan bir sınıf yazmak isteyeceksiniz. Bu tür sınıfların kötü bir ünü vardır, çünkü bazı insanlar nesneler cinsinden düşünmekten kaçınmak için bunları kötüye kullanır; yine de geçerli kullanım alanları vardır.
private MyStaticClass() {
// private constructor to prevent instantiation
}
6. Gereksiz nesneler oluşturmaktan kaçının
String s = new String("bikini"); // DON'T DO THIS!
Ya da:
// Hideously slow! Can you spot the object creation?
private static long sum() {
Long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++)
sum += i;
return sum;
}
Çözüm: Bu program doğru sonucu verir, ama tek karakterlik bir yazım hatası yüzünden olması gerekenden çok daha yavaştır. sum değişkeni long yerine Long olarak tanımlanmıştır; bu da programın yaklaşık 2^31 gereksiz Long örneği oluşturduğu anlamına gelir (kabaca long i değeri Long sum’a her eklendiğinde bir tane).
7. Finalizer ve cleaner’lardan kaçının
Finalizer’lar öngörülemez, çoğu zaman tehlikeli ve genellikle gereksizdir. Kullanımları düzensiz davranışlara, düşük performansa ve taşınabilirlik sorunlarına yol açabilir.
8. try-finally yerine try-with-resources tercih edin
// try-with-resources - the best way to close resources!
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
9. Comparable arayüzünü uygulamayı değerlendirin
Comparable’ı uyguladığınızda sınıfınızın, bu arayüze dayanan çok sayıdaki generic algoritma ve collection implementasyonuyla birlikte çalışabilmesini sağlarsınız.
10. Sınıfların ve üyelerin erişilebilirliğini en aza indirin
İyi tasarlanmış bir bileşeni kötü tasarlanmış olandan ayıran en önemli tek etken, bileşenin kendi iç verilerini ve diğer uygulama ayrıntılarını diğer bileşenlerden ne ölçüde gizlediğidir. İyi tasarlanmış bir bileşen tüm uygulama ayrıntılarını gizler ve API’sini implementasyonundan temiz bir şekilde ayırır. Bileşenler böylece yalnızca API’leri üzerinden iletişim kurar ve birbirlerinin iç işleyişinden habersizdir. Bilgi gizleme veya kapsülleme olarak bilinen bu kavram, yazılım tasarımının temel ilkelerinden biridir.
11. Public sınıflarda public alanlar yerine erişim metotları kullanın
Zaman zaman, örnek alanlarını gruplamaktan başka hiçbir amaca hizmet etmeyen dejenere sınıflar yazmak isteyebilirsiniz:
// Degenerate classes like this should not be public!
class Point {
public double x;
public double y;
}
Bu tür sınıfların veri alanlarına doğrudan erişildiği için kapsüllemenin faydalarını sunmazlar. API’yi değiştirmeden temsili değiştiremez, değişmezleri (invariant) zorunlu kılamaz ve bir alana erişildiğinde ek bir işlem yapamazsınız. Katı nesne yönelimli programcılar bu tür sınıfları lanetli sayar ve her zaman private alanlara ve public erişim metotlarına (getter’lar), değiştirilebilir sınıflar için de değiştirici metotlara (setter’lar) sahip sınıflarla değiştirilmeleri gerektiğini düşünür:
// Encapsulation of data by accessor methods and mutators
class Point {
private double x;
private double y;
public Point(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() { return x; }
public double getY() { return y; }
public void setX(double x) { this.x = x; }
public void setY(double y) { this.y = y; }
}
Public sınıflar söz konusu olduğunda katı görüşlüler kesinlikle haklıdır: Bir sınıf kendi paketinin dışından erişilebiliyorsa, sınıfın iç temsilini değiştirme esnekliğini korumak için erişim metotları sağlayın. Public bir sınıf veri alanlarını dışarı açarsa, istemci kodu her yere dağılmış olabileceğinden temsilini değiştirme umudu tamamen ortadan kalkar.
Ancak bir sınıf package-private ise ya da private bir iç içe (nested) sınıfsa, veri alanlarını dışarı açmakta – sınıfın sunduğu soyutlamayı yeterince iyi tarif ettikleri sürece – özünde yanlış bir şey yoktur.
12. Değiştirilebilirliği en aza indirin
Immutable (değiştirilemez) bir sınıf, örnekleri değiştirilemeyen bir sınıftır. Her örneğin içerdiği tüm bilgiler nesnenin ömrü boyunca sabittir, dolayısıyla hiçbir değişiklik gözlemlenemez. Java platform kütüphaneleri; String, kutulanmış (boxed) primitif sınıflar, BigInteger ve BigDecimal dahil pek çok immutable sınıf içerir. Bunun birçok iyi nedeni vardır: Immutable sınıfları tasarlamak, uygulamak ve kullanmak, değiştirilebilir sınıflara göre daha kolaydır. Hataya daha az açık ve daha güvenlidirler. Bir sınıfı immutable yapmak için şu beş kurala uyun:
- Nesnenin state’ini değiştiren metotlar sunmayın (mutator olarak bilinirler).
-
Sınıfın genişletilemeyeceğinden emin olun. Bu, dikkatsiz veya kötü niyetli alt sınıfların, nesnenin state’i değişmiş gibi davranarak sınıfın immutable davranışını
bozmasını önler. Alt sınıf oluşturmanın önüne geçmek genellikle sınıfı örneğin
finalyaparak sağlanır. - Tüm alanları final yapın. Bu, niyetinizi sistem tarafından zorunlu kılınan bir biçimde açıkça ifade eder. Ayrıca, yeni oluşturulmuş bir örneğe ait bir referans senkronizasyon olmadan bir thread’den diğerine aktarılırsa, bellek modelinde açıklandığı gibi doğru davranışı güvence altına almak için de gereklidir.
- Tüm alanları private yapın. Bu, istemcilerin alanların referans verdiği değiştirilebilir nesnelere erişip bunları doğrudan değiştirmesini önler. Immutable sınıfların primitif değerler ya da immutable nesnelere referanslar içeren public final alanlara sahip olması teknik olarak mümkün olsa da önerilmez, çünkü sonraki bir sürümde iç temsili değiştirmeyi imkânsız kılar.
-
Değiştirilebilir bileşenlere münhasır erişim sağlayın. Sınıfınızın değiştirilebilir nesnelere referans veren alanları varsa, sınıfın istemcilerinin bu nesnelere
referans elde edemeyeceğinden emin olun. Böyle bir alanı asla istemcinin verdiği bir nesne referansıyla başlatmayın ya da bir erişim metodundan döndürmeyin. Constructor’larda, erişim metotlarında ve
readObjectmetotlarında savunmacı kopyalar (defensive copy) oluşturun.
13. Kalıtım yerine kompozisyonu tercih edin
Kalıtım, kodun yeniden kullanımını sağlamanın güçlü bir yoludur, ama iş için her zaman en iyi araç değildir. Uygunsuz kullanıldığında kırılgan yazılımlara yol açar. Alt sınıf ve üst sınıf implementasyonlarının aynı programcıların kontrolünde olduğu bir paket içinde kalıtım kullanmak güvenlidir. Özellikle genişletilmek üzere tasarlanmış ve belgelenmiş sınıfları genişletirken kalıtım kullanmak da güvenlidir. Ancak sıradan somut sınıflardan paket sınırları ötesinde kalıtım almak tehlikelidir.
Metot çağrısının aksine, kalıtım kapsüllemeyi ihlal eder. Yalnızca yeni metotlar ekleyip mevcut metotları override etmekten kaçınırsanız bir sınıfı genişletmenin güvenli olduğunu düşünebilirsiniz. Bu tür bir genişletme çok daha güvenli olsa da risksiz değildir. Üst sınıf sonraki bir sürümde yeni bir metot kazanırsa ve siz talihsizlik eseri alt sınıfa aynı imzaya ama farklı dönüş tipine sahip bir metot vermişseniz, alt sınıfınız artık derlenmez. Alt sınıfa, yeni üst sınıf metoduyla aynı imzaya ve dönüş tipine sahip bir metot vermişseniz, artık onu override ediyorsunuz demektir; dolayısıyla daha önce anlatılan sorunlarla karşı karşıyasınız. Üstelik metodunuzun yeni üst sınıf metodunun sözleşmesini yerine getirmesi şüphelidir, çünkü siz alt sınıf metodunu yazdığınızda bu sözleşme henüz yazılmamıştı.
Neyse ki yukarıda anlatılan tüm sorunlardan kaçınmanın bir yolu var. Mevcut bir sınıfı genişletmek yerine, yeni sınıfınıza mevcut sınıfın bir örneğine referans veren private bir alan ekleyin. Bu tasarıma kompozisyon denir, çünkü mevcut sınıf yeni sınıfın bir bileşeni hâline gelir. Yeni sınıftaki her örnek metodu, içerilen mevcut sınıf örneği üzerinde karşılık gelen metodu çağırır ve sonuçları döndürür. Buna yönlendirme (forwarding), yeni sınıftaki metotlara da yönlendirme metotları denir. Ortaya çıkan sınıf kaya gibi sağlam olur ve mevcut sınıfın uygulama ayrıntılarına hiçbir bağımlılığı bulunmaz. Mevcut sınıfa yeni metotlar eklemek bile yeni sınıfı etkilemez.
14. Soyut sınıflar yerine arayüzleri tercih edin
Java’da birden fazla implementasyona izin veren bir tip tanımlamak için iki mekanizma vardır: arayüzler ve soyut sınıflar. Java 8 ile arayüzlere default metotların gelmesinden bu yana her iki mekanizma da bazı örnek metotları için implementasyon sağlamanıza olanak tanır.
Önemli bir fark şudur: Soyut bir sınıfın tanımladığı tipi uygulamak için bir sınıfın o soyut sınıfın alt sınıfı olması gerekir. Java yalnızca tekli kalıtıma izin verdiğinden, soyut sınıflara getirilen bu kısıtlama, onların tip tanımı olarak kullanımını ciddi biçimde sınırlar. Gerekli tüm metotları tanımlayan ve genel sözleşmeye uyan her sınıf, sınıf hiyerarşisinde nerede bulunduğundan bağımsız olarak bir arayüzü uygulayabilir. Mevcut sınıflar, yeni bir arayüzü uygulayacak şekilde kolayca güncellenebilir.
- Arayüzler, mixin tanımlamak için idealdir.
- Arayüzler, hiyerarşik olmayan tip yapıları kurmayı mümkün kılar.
- Arayüzler, wrapper sınıf deyimi aracılığıyla güvenli ve güçlü işlevsellik geliştirmelerine olanak tanır. Tipleri tanımlamak için soyut sınıflar kullanırsanız, işlevsellik eklemek isteyen programcıya kalıtımdan başka seçenek bırakmazsınız.
15. Generic’ler: Raw type kullanmayın
Hatayı ancak çalışma zamanında, gerçekleşmesinden çok sonra ve hatayı içeren koddan uzak olabilecek bir kodda fark edersiniz. ClassCastException’ı gördüğünüzde, madeni parayı pul koleksiyonuna koyan metot çağrısını bulmak için kod tabanını taramanız gerekir. Derleyici size yardımcı olamaz.
16. Generic’ler: Unchecked uyarılarını ortadan kaldırın
Generic’lerle programlama yaptığınızda çok sayıda derleyici uyarısı görürsünüz:
- unchecked cast uyarıları
- unchecked metot çağrısı uyarıları
- unchecked parametreli vararg tipi uyarıları
- unchecked dönüşüm uyarıları.
Generic’lerle deneyiminiz arttıkça daha az uyarı alırsınız, ama yeni yazılmış kodun tertemiz derlenmesini beklemeyin. Birçok unchecked uyarısını gidermek kolaydır. Örneğin, yanlışlıkla şu tanımı yazdığınızı varsayalım:
Set<Lark> exaltation = new HashSet();
Bunu şöyle değiştirin:
Set<Lark> exaltation = new HashSet<>();
Bazı uyarıları gidermek çok daha zor olacaktır. Biraz düşünmeyi gerektiren uyarılar aldığınızda pes etmeyin! Giderebildiğiniz her unchecked uyarısını giderin.
Tüm uyarıları giderirseniz, kodunuzun typesafe olduğundan emin olursunuz; bu da çok iyi bir şeydir. Çalışma zamanında ClassCastException almayacağınız anlamına gelir ve programınızın
istediğiniz gibi davranacağına olan güveninizi artırır. Bir uyarıyı gideremiyor, ama uyarıya yol açan kodun typesafe olduğunu kanıtlayabiliyorsanız, o zaman (ve yalnızca o zaman) uyarıyı
bir @SuppressWarnings("unchecked") anotasyonuyla bastırın. Kodun typesafe olduğunu önce kanıtlamadan uyarıları bastırırsanız, kendinize sahte bir güvenlik
duygusu vermiş olursunuz.
Kod hiçbir uyarı vermeden derlenebilir, ama çalışma zamanında yine de ClassCastException fırlatabilir.
@SuppressWarnings("unchecked") anotasyonunu her kullandığınızda, bunun neden güvenli olduğunu açıklayan bir yorum ekleyin.
17. Generic’ler: Ne zaman anlamlı olduğu
Eyvah – generic’lere fena hâlde ihtiyaç duyan bir sınıf:
Böyle bir şey en geç merge request aşamasında yakalanmalı!
18. Generic’ler: Nasıl yapılmamalı
Kusura bakmayın, söze gerek yok. Lütfen bunu ASLA merge etmeyin:
public abstract class JpaLearningCardBaseContentService<LC extends LearningCardBaseContent<L, LC, AC>, J extends JpaLearningCardBaseContent<L, LC, AC>, L extends
LearningCardBase<LC, A>, A extends AnswerOptionBase<L, AC>, AC extends AnswerOptionBaseContent<LC, AC, A>, AS extends AnswerOptionBaseService<A, AC, L, LC>, ACS extends
AnswerOptionBaseContentService<AC, A, L, LC>, LS extends LearningCardBaseService<L>>
extends JpaLightPersistenceService<LC, J, Long> implements LearningCardBaseContentService<LC, L, A, AC> {
// ...
}
Basit bir sınıf tanımını anlamak asla 5 dakika sürmemeli.
19. Diziler yerine listeleri tercih edin
Diziler, generic tiplerden iki önemli açıdan ayrılır. Birincisi, diziler kovaryanttır. Kulağa korkutucu gelen bu kelime basitçe şu anlama gelir: Sub, Super’ın bir alt tipiyse,
Sub[] dizi tipi de Super[] dizi tipinin bir alt tipidir. Generic’ler ise invaryanttır: Birbirinden farklı herhangi iki tip Type1 ve Type2 için
List<Type1>, List<Type2> tipinin ne alt tipi ne de üst tipidir.
20. Anonim sınıflar yerine lambda’ları tercih edin
// Anonymous class instance as a function object - obsolete!
Collections.sort(words, new Comparator<String>() {
public int compare(String s1, String s2) {
return Integer.compare(s1.length(), s2.length());
}
});
Bunun yerine şunu yapın:
// Lambda expression as function object (replaces anonymous class)
Collections.sort(words, (s1, s2) -> Integer.compare(s1.length(), s2.length()));
21. Yerel değişkenlerin kapsamını en aza indirin
Bir yerel değişkenin kapsamını en aza indirmenin en etkili tekniği, onu ilk kullanıldığı yerde tanımlamaktır...
22. Geleneksel for döngüleri yerine for-each döngülerini tercih edin
// The preferred idiom for iterating over collections and arrays
for (Element e : elements) {
... // Do something with e
}
23. Kütüphaneleri tanıyın ve kullanın
Sıfır ile bir üst sınır arasında rastgele tam sayılar üretmek istediğinizi varsayalım. Bu yaygın görevle karşılaşan birçok programcı, aşağı yukarı şuna benzeyen küçük bir metot yazar:
// Common but deeply flawed!
static Random rnd = new Random();
static int random(int n) {
return Math.abs(rnd.nextInt()) % n;
}
Bu metot iyi görünebilir, ama üç kusuru vardır. Birincisi, n ikinin küçük bir kuvvetiyse, rastgele sayı dizisi oldukça kısa bir periyottan sonra kendini tekrar eder. İkinci kusur, n ikinin bir kuvveti değilse bazı sayıların ortalamada diğerlerinden daha sık döndürülmesidir. n büyükse bu etki oldukça belirgin olabilir.
random metodunun üçüncü kusuru, nadir durumlarda feci şekilde başarısız olup belirtilen aralığın dışında bir sayı döndürebilmesidir. Bunun nedeni,
metodun rnd.nextInt() tarafından döndürülen değeri Math.abs çağırarak negatif olmayan bir int değerine eşlemeye çalışmasıdır. nextInt()
Integer.MIN_VALUE döndürürse, Math.abs de Integer.MIN_VALUE döndürür ve kalan operatörü (%), n ikinin bir kuvveti
değilse negatif bir sayı döndürür. Bu neredeyse kesinlikle programınızın çökmesine yol açar ve hatayı yeniden üretmek zor olabilir.
24. Kesin sonuç gerekiyorsa float ve double kullanmaktan kaçının
Parasal tutarlar için BigDecimal kullanın. float ve double yuvarlama hatalarına yol açar.
Örneğin cebinizde 1,03 $ olduğunu ve 42 ¢ harcadığınızı varsayalım. Geriye ne kadar paranız kalır? İşte bu soruyu yanıtlamaya çalışan saf bir program parçası:
System.out.println(1.03 - 0.42);
Ne yazık ki ekrana 0.6100000000000001 yazdırır. Bu tek örnek de değil. Cebinizde bir dolar olduğunu ve tanesi on sent olan dokuz rondela aldığınızı varsayalım. Ne kadar
para üstü alırsınız?
System.out.println(1.00 - 9 * 0.10);
Bu program parçasına göre $0.09999999999999998 alırsınız.
Sorunun, sonuçları yazdırmadan önce yuvarlayarak çözülebileceğini düşünebilirsiniz, ama ne yazık ki bu her zaman işe yaramaz. Örneğin cebinizde bir dolar olduğunu ve rafta fiyatları 10 ¢, 20 ¢, 30 ¢ diye bir dolara kadar giden bir sıra nefis şekerleme gördüğünüzü varsayalım. 10 ¢ olandan başlayarak her şekerlemeden birer tane alıyorsunuz, ta ki raftaki bir sonrakine paranız yetmeyene kadar.
25. Kutulanmış primitifler yerine primitif tipleri tercih edin
// Broken comparator - can you spot the flaw?
Comparator<Integer> naturalOrder = (i, j) -> (i < j) ? -1 : (i == j ? 0 : 1);
26. Nesnelere arayüzleri üzerinden referans verin
İyi örnek:
Set<Son> sonSet = new LinkedHashSet<>();
Kötü örnek:
LinkedHashSet<Son> sonSet = new LinkedHashSet<>();
27. Reflection yerine arayüzleri tercih edin
Aksi hâlde:
- Derleme zamanı tip denetiminin tüm avantajlarını kaybedersiniz; exception denetimi de buna dahildir. Bir program var olmayan veya erişilemeyen bir metodu reflection ile çağırmaya çalışırsa, özel önlemler almadıysanız çalışma zamanında hata verir.
- Reflection ile erişim için gereken kod hantal ve uzundur. Yazması sıkıcı, okuması zordur.
- Performans düşer. Reflection ile metot çağrısı, normal metot çağrısından çok daha yavaştır.
28. Genel kabul görmüş isimlendirme kurallarına uyun
Java platformunun köklü bir isimlendirme kuralları seti vardır; bunların birçoğu The Java Language Specification’da [JLS, 6.1] yer alır.
29. Exception’ları yalnızca istisnai durumlar için kullanın
// Horrible abuse of exceptions. Don't ever do this!
try {
int i = 0;
while (true) {
range[i++].climb();
}
} catch (ArrayIndexOutOfBoundsException e) {
// catch it
}
Bu kod ne yapıyor? İncelemekle hiç de anlaşılmıyor ve bu, onu kullanmamak için yeterli bir neden. Meğer bir dizinin elemanları üzerinde döngü kurmak için korkunç derecede kötü düşünülmüş
bir deyimmiş. Sonsuz döngü, dizinin sınırları dışındaki ilk elemana erişmeye çalıştığında bir ArrayIndexOutOfBoundsException fırlatıp yakalayarak ve görmezden gelerek
sona eriyor. Bir dizi üzerinde döngü kurmanın, her Java programcısının anında tanıyacağı standart deyimine denk olması amaçlanmış:
for (Mountain m : range) {
m.climb();
}
Exception’lar, adlarından da anlaşılacağı gibi, yalnızca istisnai durumlar için kullanılmalıdır; asla olağan kontrol akışı için kullanılmamalıdır.
30. Kurtarılabilir durumlar için checked exception’ları, programlama hataları için runtime exception’ları kullanın
Çağıranın makul olarak toparlanabileceği durumlar için checked exception kullanın. API tasarımcısı, kullanıcıyı bir checked exception ile karşı karşıya bırakarak o durumdan toparlanma yükümlülüğü getirir. Kullanıcı exception’ı yakalayıp görmezden gelerek bu yükümlülüğü yok sayabilir, ama bu genellikle kötü bir fikirdir.
Programlama hatalarını belirtmek için runtime exception kullanın. Runtime exception’ların büyük çoğunluğu ön koşul ihlallerini belirtir. Ön koşul ihlali, basitçe bir API’nin
istemcisinin API spesifikasyonunun belirlediği sözleşmeye uymamasıdır. Örneğin dizi erişimi sözleşmesi, dizi indeksinin sıfır ile dizi uzunluğunun bir eksiği arasında (sınırlar dahil)
olması gerektiğini belirtir. ArrayIndexOutOfBoundsException, bu ön koşulun ihlal edildiğini gösterir.
31. Checked exception’ların gereksiz kullanımından kaçının
Özetle: Ölçülü kullanıldığında checked exception’lar programların güvenilirliğini artırabilir; aşırı kullanıldığında ise API’lerin kullanımını eziyete çevirir.
Pair<Boolean, LearningCardGroup> answerLearningCard(@NotNull LearningCardGroup learningCardGroup,
LearningCardAssignment learningCardAssignment,
@NotNull Collection<AnswerOption> answers)
throws InvalidTrainingStateException, PersistenceException, LearningCardGroupAlreadyCompletedException,
ConsecutiveCorrectCountExceededException,
PreviousLearningCardGroupUncompletedException, LearningCardGroupCooldownException,
LearningCardGroupHasUncompletedLearningCardsException, InvalidParticipantException, TrainingNotSignedException; // but why ???
// this is a mistake seen a lot with junior devs
// exceptions are exceptionally slow and should be avoided for mere validation
// the performance gets even worse if a stacktrace is printed, since logging and printing is one of the slowest possible actions
32. Standart exception’ları kullanmayı tercih edin
Uzman programcıları daha az deneyimli olanlardan ayıran bir özellik, uzmanların yüksek düzeyde kod yeniden kullanımı için çabalaması ve genellikle bunu başarmasıdır.
Exception’lar da, kodun yeniden kullanımının iyi bir şey olduğu kuralının istisnası değildir. Java kütüphaneleri, çoğu API’nin exception fırlatma ihtiyaçlarının büyük kısmını karşılayan bir exception seti sunar. Standart exception’ları yeniden kullanmanın birçok faydası vardır. Bunların başında, API’nizin programcıların zaten aşina olduğu yerleşik kurallarla örtüşmesi sayesinde öğrenilmesinin ve kullanılmasının kolaylaşması gelir. Hemen ardından, API’nizi kullanan programların alışılmadık exception’larla dolu olmadığı için daha kolay okunması gelir. Son olarak (ve en önemsiz olarak), daha az exception sınıfı daha küçük bir bellek ayak izi ve sınıfları yüklemeye harcanan daha az zaman demektir.
En sık yeniden kullanılan exception tipi IllegalArgumentException’dır. Bu, genellikle çağıran, değeri uygunsuz bir argüman verdiğinde fırlatılacak exception’dır. Örneğin
çağıran, bir işlemin kaç kez tekrarlanacağını belirten bir parametreye negatif bir sayı verirse fırlatılacak exception budur. Sık yeniden kullanılan bir diğer
exception IllegalStateException’dır. Bu, genellikle çağrı, çağrıyı alan nesnenin state’i nedeniyle geçersizse fırlatılacak exception’dır. Örneğin çağıran, bir nesneyi
düzgün şekilde başlatılmadan önce kullanmaya çalışırsa fırlatılacak exception budur.
Tartışmaya açık olsa da her hatalı metot çağrısı özünde geçersiz bir argümana veya state’e indirgenir; ancak belirli türdeki geçersiz argümanlar ve state’ler için standart olarak başka exception’lar kullanılır.
Çağıran, null değerlerin yasak olduğu bir parametreye null verirse, teamül olarak IllegalArgumentException yerine NullPointerException fırlatılır.
Benzer şekilde, çağıran bir dizideki indeksi temsil eden bir parametreye aralık dışında bir değer verirse, IllegalArgumentException yerine IndexOutOfBoundsException fırlatılmalıdır.
Exception, RuntimeException, Throwable veya Error sınıflarını doğrudan yeniden kullanmayın. Bu sınıflara soyutmuş gibi davranın. Gerçi checked bir exception’ı runtime
exception’a dönüştürmek istiyorsanız ve ne yaptığınızı gerçekten biliyorsanız RuntimeException kullanılabilir.
33. Exception’ları görmezden gelmeyin
// Empty catch block ignores exception - Highly suspect!
try {
...
} catch (SomeException e) {
}
Boş bir catch bloğu exception’ların amacını boşa çıkarır; bu amaç, sizi istisnai durumları ele almaya zorlamaktır. Bir exception’ı görmezden gelmek, bir yangın alarmını görmezden gelmeye – ve başka kimse gerçek bir yangın olup olmadığını göremesin diye alarmı kapatmaya – benzer.