“Effective Java (3rd Edition)” से Best Practices
Joshua Bloch की किताब “Effective Java (3rd Edition) ” से अंश
1. null के बजाय खाली collections या arrays return करें
अगर method एक Set return करता है, तो खाली नतीजे के लिए return null; न करें – इसके बजाय return Set.of(); करें।
2. Constructors के बजाय static factory methods पर विचार करें
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
ये हमेशा बेहतर नहीं होते, लेकिन कुछ use cases में कई फ़ायदे देते हैं:
- Static factory methods का एक फ़ायदा यह है कि constructors के उलट इनके नाम होते हैं।
- हर बार call होने पर इन्हें नया object बनाना ज़रूरी नहीं होता।
- ये अपने return type के किसी भी subtype का object return कर सकते हैं।
- Input parameters के आधार पर, return होने वाले object की class हर call में अलग हो सकती है।
- Method वाली class लिखते समय, return होने वाले object की class का मौजूद होना ज़रूरी नहीं है।
नुकसान:
- सिर्फ़ static factory methods देने की मुख्य सीमा यह है कि public या protected constructors के बिना classes को subclass नहीं किया जा सकता।
- Static factory methods की दूसरी कमी यह है कि programmers के लिए इन्हें ढूँढना मुश्किल होता है।
3. Constructor parameters बहुत ज़्यादा हों, तो builder पर विचार करें
Static factories और constructors की एक साझा सीमा है: बड़ी संख्या में optional parameters के साथ ये अच्छी तरह scale नहीं करते। जिन projects पर मैंने काम किया, उनमें से कुछ पर इसकी बहुत भारी मार पड़ी।
4. Singleton property को private constructor या enum type से लागू करें
जिस चीज़ के कई instances की ज़रूरत नहीं है, उसे सिर्फ़ एक बार instantiate किया जाना चाहिए – चाहे @ApplicationScoped से या किसी दूसरे singleton pattern से।
5. Private constructor से noninstantiability लागू करें
कभी-कभी आप ऐसी class लिखना चाहेंगे, जो सिर्फ़ static methods और static fields का एक समूह हो। ऐसी classes बदनाम हो चुकी हैं, क्योंकि कुछ लोग objects के रूप में सोचने से बचने के लिए इनका दुरुपयोग करते हैं, लेकिन इनके जायज़ इस्तेमाल भी हैं।
private MyStaticClass() {
// private constructor to prevent instantiation
}
6. ग़ैर-ज़रूरी objects बनाने से बचें
String s = new String("bikini"); // DON'T DO THIS!
या:
// 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;
}
हल: यह program सही जवाब देता है, लेकिन एक अक्षर की टाइपिंग की गलती की वजह से यह जितना होना चाहिए, उससे कहीं ज़्यादा धीमा है। Variable sum को long के बजाय Long के रूप में declare किया गया है, यानी program लगभग 2^31 ग़ैर-ज़रूरी Long instances बनाता है (मोटे तौर पर हर उस बार एक, जब long i को Long sum में जोड़ा जाता है)।
7. Finalizers और cleaners से बचें
Finalizers का व्यवहार अप्रत्याशित, अक्सर ख़तरनाक और आम तौर पर ग़ैर-ज़रूरी होता है। इनके इस्तेमाल से अनियमित व्यवहार, खराब performance और portability की समस्याएँ हो सकती हैं।
8. try-finally के बजाय try-with-resources को प्राथमिकता दें
// try-with-resources - the best way to close resources!
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
return br.readLine();
}
9. Comparable implement करने पर विचार करें
Comparable implement करके आप अपनी class को उन सभी generic algorithms और collection implementations के साथ काम करने देते हैं, जो इस interface पर निर्भर हैं।
10. Classes और members की accessibility कम से कम रखें
अच्छी तरह design किए गए component को खराब design वाले component से अलग करने वाला सबसे अहम पहलू यह है कि वह component अपने internal data और दूसरे implementation details को बाकी components से किस हद तक छिपाता है। अच्छी तरह design किया गया component अपने सारे implementation details छिपाता है और अपनी API को अपने implementation से साफ़ तौर पर अलग रखता है। फिर components सिर्फ़ अपनी APIs के ज़रिए बात करते हैं और एक-दूसरे के अंदरूनी कामकाज से अनजान रहते हैं। यह अवधारणा, जिसे information hiding या encapsulation कहा जाता है, software design का एक बुनियादी सिद्धांत है।
11. Public classes में public fields नहीं, accessor methods इस्तेमाल करें
कभी-कभी आपका मन ऐसी अधकचरी (degenerate) classes लिखने का कर सकता है, जिनका instance fields को एक जगह रखने के अलावा कोई मकसद नहीं होता:
// Degenerate classes like this should not be public!
class Point {
public double x;
public double y;
}
चूँकि ऐसी classes के data fields सीधे access होते हैं, इसलिए ये encapsulation के फ़ायदे नहीं देतीं। आप API बदले बिना representation नहीं बदल सकते, invariants लागू नहीं कर सकते, और किसी field के access होने पर कोई अतिरिक्त कदम नहीं उठा सकते। कट्टर object-oriented programmers मानते हैं कि ऐसी classes बिल्कुल वर्जित हैं और इन्हें हमेशा private fields और public accessor methods (getters) वाली classes से बदल देना चाहिए, और mutable classes के लिए mutators (setters) से भी:
// 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 classes के मामले में ये कट्टरपंथी बिल्कुल सही हैं: अगर कोई class अपने package के बाहर accessible है, तो accessor methods दें, ताकि class के internal representation को बदलने का लचीलापन बना रहे। अगर कोई public class अपने data fields expose करती है, तो उसका representation बदलने की हर उम्मीद ख़त्म हो जाती है, क्योंकि client code दूर-दूर तक फैल चुका हो सकता है।
लेकिन अगर class package-private है या एक private nested class है, तो उसके data fields expose करने में अपने-आप में कुछ भी गलत नहीं है – बशर्ते वे class के दिए abstraction को ठीक से बयान करते हों।
12. Mutability कम से कम रखें
Immutable class बस एक ऐसी class है, जिसके instances को बदला नहीं जा सकता। हर instance में मौजूद सारी जानकारी object के पूरे जीवनकाल के लिए तय होती है, इसलिए कोई भी बदलाव कभी दिखाई नहीं देता। Java platform libraries में कई immutable classes हैं, जैसे String, boxed primitive classes, और BigInteger व BigDecimal। इसके कई अच्छे कारण हैं: immutable classes को design, implement और इस्तेमाल करना mutable classes से आसान है। इनमें गलतियों की गुंजाइश कम होती है और ये ज़्यादा सुरक्षित होती हैं। किसी class को immutable बनाने के लिए इन पाँच नियमों का पालन करें:
- ऐसे methods न दें, जो object की state को बदलते हों (इन्हें mutators कहा जाता है)।
-
पक्का करें कि class को extend न किया जा सके। इससे लापरवाह या दुर्भावनापूर्ण subclasses ऐसा व्यवहार करके class के immutable व्यवहार को बिगाड़ नहीं पातीं, मानो object की
state बदल गई हो। Subclassing को आम तौर पर रोका जाता है, उदाहरण के लिए class को
finalबनाकर। - सभी fields को final बनाएँ। इससे आपका इरादा साफ़ ज़ाहिर होता है, और system खुद इसे लागू करता है। साथ ही, अगर किसी नए बने instance का reference बिना synchronization के एक thread से दूसरे thread को दिया जाता है, तो सही व्यवहार सुनिश्चित करने के लिए भी यह ज़रूरी है, जैसा कि memory model में बताया गया है।
- सभी fields को private बनाएँ। इससे clients उन mutable objects तक पहुँच नहीं पाते, जिन्हें fields refer करते हैं, और उन्हें सीधे बदल भी नहीं पाते। तकनीकी रूप से immutable classes में primitive values या immutable objects के references वाले public final fields रखना जायज़ है, लेकिन इसकी सलाह नहीं दी जाती, क्योंकि इससे बाद की किसी release में internal representation बदलना संभव नहीं रहता।
-
किसी भी mutable component तक exclusive access सुनिश्चित करें। अगर आपकी class में ऐसे fields हैं जो mutable objects को refer करते हैं, तो पक्का करें कि class के clients को
उन objects के references न मिल सकें। ऐसे किसी field को कभी भी client के दिए object reference से initialize न करें और न ही उस field को किसी accessor से return करें। Constructors, accessors और
readObjectmethods में defensive copies बनाएँ।
13. Inheritance के बजाय composition को प्राथमिकता दें
Inheritance कोड को दोबारा इस्तेमाल करने का एक ताक़तवर तरीका है, लेकिन यह हमेशा इस काम के लिए सबसे अच्छा टूल नहीं होता। गलत तरीके से इस्तेमाल करने पर यह नाज़ुक (fragile) software की ओर ले जाता है। किसी package के अंदर inheritance का इस्तेमाल सुरक्षित है, जहाँ subclass और superclass के implementations एक ही programmers के नियंत्रण में हों। उन classes को extend करते समय भी inheritance सुरक्षित है, जिन्हें ख़ास तौर पर extension के लिए design और document किया गया हो। लेकिन package की सीमाओं के पार सामान्य concrete classes से inherit करना ख़तरनाक है।
Method invocation के उलट, inheritance encapsulation को तोड़ता है। आप सोच सकते हैं कि अगर आप सिर्फ़ नए methods जोड़ें और मौजूदा methods को override न करें, तो class को extend करना सुरक्षित है। हालाँकि इस तरह का extension कहीं ज़्यादा सुरक्षित है, फिर भी यह जोखिम से ख़ाली नहीं है। अगर superclass को आगे की किसी release में एक नया method मिल जाए, और आपकी बदकिस्मती से आपने subclass को उसी signature लेकिन अलग return type वाला method दे रखा हो, तो आपकी subclass compile होना बंद हो जाएगी। अगर आपने subclass को नए superclass method जैसे ही signature और return type वाला method दिया है, तो अब आप उसे override कर रहे हैं, यानी आप पहले बताई गई समस्याओं के दायरे में आ जाते हैं। इसके अलावा, इस पर भी संदेह है कि आपका method नए superclass method के contract को पूरा करेगा, क्योंकि जब आपने subclass method लिखा था, तब वह contract लिखा ही नहीं गया था।
सौभाग्य से, ऊपर बताई गई सभी समस्याओं से बचने का एक तरीका है। किसी मौजूदा class को extend करने के बजाय, अपनी नई class को एक private field दें, जो मौजूदा class के एक instance को refer करे। इस design को composition कहते हैं, क्योंकि मौजूदा class नई class का एक component बन जाती है। नई class का हर instance method, मौजूदा class के contained instance पर संबंधित method को call करता है और नतीजे return करता है। इसे forwarding कहते हैं, और नई class के methods को forwarding methods कहा जाता है। इससे बनी class चट्टान की तरह मज़बूत होगी और मौजूदा class के implementation details पर उसकी कोई निर्भरता नहीं होगी। मौजूदा class में नए methods जोड़ने का भी नई class पर कोई असर नहीं पड़ेगा।
14. Abstract classes के बजाय interfaces को प्राथमिकता दें
Java में ऐसा type define करने के दो mechanisms हैं, जिसके कई implementations हो सकें: interfaces और abstract classes। Java 8 में interfaces के लिए default methods आने के बाद से, दोनों mechanisms आपको कुछ instance methods के implementations देने की सुविधा देते हैं।
एक बड़ा अंतर यह है कि abstract class से define हुए type को implement करने के लिए class का उस abstract class की subclass होना ज़रूरी है। चूँकि Java सिर्फ़ single inheritance की अनुमति देता है, इसलिए abstract classes पर यह पाबंदी type definitions के रूप में उनके इस्तेमाल को बहुत सीमित कर देती है। कोई भी class, जो सभी ज़रूरी methods define करती है और general contract का पालन करती है, interface को implement कर सकती है – चाहे वह class hierarchy में कहीं भी हो। मौजूदा classes में आसानी से बदलाव करके उनसे नया interface implement कराया जा सकता है।
- Interfaces, mixins define करने के लिए आदर्श हैं।
- Interfaces से nonhierarchical type frameworks बनाए जा सकते हैं।
- Interfaces सुरक्षित और ताक़तवर functionality enhancements संभव बनाते हैं – wrapper class idiom के ज़रिए। अगर आप types define करने के लिए abstract classes का इस्तेमाल करते हैं, तो functionality जोड़ना चाहने वाले programmer के पास inheritance के अलावा कोई विकल्प नहीं बचता।
15. Generics: raw types का इस्तेमाल न करें
Error का पता आपको runtime पर ही चलता है – उसके होने के काफ़ी बाद, और ऐसे कोड में जो error वाले कोड से बहुत दूर हो सकता है। ClassCastException दिखने के बाद आपको पूरे codebase में वह method invocation ढूँढना पड़ता है, जिसने सिक्के को डाक-टिकटों के संग्रह में डाल दिया था। Compiler इसमें आपकी कोई मदद नहीं कर सकता।
16. Generics: unchecked warnings ख़त्म करें
Generics के साथ programming करते समय आपको कई compiler warnings दिखेंगी:
- unchecked cast की warnings
- unchecked method invocation की warnings
- unchecked parameterized vararg type की warnings
- unchecked conversion की warnings।
Generics के साथ आपका अनुभव जितना बढ़ेगा, warnings उतनी ही कम आएँगी, लेकिन यह उम्मीद न करें कि नया लिखा कोड बिना किसी warning के compile होगा। कई unchecked warnings को ख़त्म करना आसान है। उदाहरण के लिए, मान लीजिए आपने गलती से यह declaration लिख दिया:
Set<Lark> exaltation = new HashSet();
इसे बदलकर यह कर दें:
Set<Lark> exaltation = new HashSet<>();
कुछ warnings को ख़त्म करना कहीं ज़्यादा मुश्किल होगा। जब ऐसी warnings मिलें जिन पर सोचना पड़े, तो डटे रहें! जितनी हो सके, हर unchecked warning को ख़त्म करें।
अगर आप सारी warnings ख़त्म कर देते हैं, तो आप निश्चिंत हो सकते हैं कि आपका कोड typesafe है, और यह बहुत अच्छी बात है। इसका मतलब है कि runtime पर आपको ClassCastException नहीं मिलेगा, और इससे आपका भरोसा
बढ़ता है कि आपका program वैसा ही व्यवहार करेगा जैसा आपने चाहा था। अगर आप किसी warning को ख़त्म नहीं कर सकते, लेकिन साबित कर सकते हैं कि warning पैदा करने वाला कोड typesafe है, तो (और सिर्फ़ तभी)
@SuppressWarnings("unchecked") annotation से उस warning को suppress करें। अगर आप पहले यह साबित किए बिना warnings suppress करते हैं कि कोड typesafe है, तो आप खुद को
सुरक्षा का झूठा एहसास दे रहे हैं।
कोड बिना कोई warning दिए compile हो सकता है, लेकिन runtime पर फिर भी ClassCastException throw कर सकता है।
जब भी आप @SuppressWarnings("unchecked") annotation का इस्तेमाल करें, एक कमेंट जोड़ें, जिसमें लिखा हो कि ऐसा करना सुरक्षित क्यों है।
17. Generics: कब समझदारी है
उफ़ – एक class, जिसे generics की सख़्त ज़रूरत है:
ऐसी चीज़ को हद से हद merge request पर तो पकड़ ही लिया जाना चाहिए!
18. Generics: ऐसे बिल्कुल नहीं
माफ़ कीजिए, कुछ कहने की ज़रूरत नहीं। कृपया इसे कभी भी merge न करें:
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> {
// ...
}
सिर्फ़ एक class declaration को समझने में कभी 5 मिनट नहीं लगने चाहिए।
19. Arrays के बजाय lists को प्राथमिकता दें
Arrays दो अहम तरीकों से generic types से अलग हैं। पहला, arrays covariant होते हैं। इस डरावने-से शब्द का मतलब बस इतना है कि अगर Sub, Super का subtype है, तो
array type Sub[], array type Super[] का subtype है। इसके विपरीत generics invariant होते हैं: किन्हीं भी दो अलग types Type1 और Type2 के लिए,
List<Type1> न तो List<Type2> का subtype है और न ही supertype।
20. Anonymous classes के बजाय lambdas को प्राथमिकता दें
// 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());
}
});
इसके बजाय यह करें:
// Lambda expression as function object (replaces anonymous class)
Collections.sort(words, (s1, s2) -> Integer.compare(s1.length(), s2.length()));
21. Local variables का scope कम से कम रखें
किसी local variable का scope कम करने की सबसे ताक़तवर तकनीक है उसे वहीं declare करना, जहाँ उसका पहली बार इस्तेमाल होता है...
22. पारंपरिक for loops के बजाय for-each loops को प्राथमिकता दें
// The preferred idiom for iterating over collections and arrays
for (Element e : elements) {
... // Do something with e
}
23. Libraries को जानें और इस्तेमाल करें
मान लीजिए आप शून्य और किसी ऊपरी सीमा के बीच random integers बनाना चाहते हैं। इस आम काम के लिए बहुत-से programmers कुछ इस तरह का छोटा-सा method लिख देंगे:
// Common but deeply flawed!
static Random rnd = new Random();
static int random(int n) {
return Math.abs(rnd.nextInt()) % n;
}
यह method अच्छा लग सकता है, लेकिन इसमें तीन कमियाँ हैं। पहली यह कि अगर n दो की कोई छोटी power है, तो random numbers का क्रम काफ़ी कम अंतराल के बाद खुद को दोहराने लगेगा। दूसरी कमी यह है कि अगर n दो की power नहीं है, तो औसतन कुछ numbers बाकियों की तुलना में ज़्यादा बार return होंगे। अगर n बड़ा है, तो यह असर काफ़ी साफ़ दिख सकता है।
Random method की तीसरी कमी यह है कि यह कभी-कभार बुरी तरह fail हो सकता है और तय range से बाहर का number return कर सकता है। ऐसा इसलिए होता है, क्योंकि यह
method Math.abs call करके rnd.nextInt() से लौटी value को एक non-negative int में map करने की कोशिश करता है। अगर nextInt()
Integer.MIN_VALUE return करता है, तो Math.abs भी Integer.MIN_VALUE ही return करेगा, और remainder operator (%) एक negative number return करेगा – यह मानते हुए कि
n दो की power नहीं है। लगभग तय है कि इससे आपका program fail होगा, और उस failure को दोबारा reproduce करना मुश्किल हो सकता है।
24. अगर सटीक जवाब चाहिए, तो float और double से बचें
पैसों की रकम के लिए BigDecimal इस्तेमाल करें। float और double rounding errors पैदा करते हैं।
उदाहरण के लिए, मान लीजिए आपकी जेब में $1.03 हैं और आप 42¢ ख़र्च करते हैं। आपके पास कितने पैसे बचे? इस सवाल का जवाब देने की कोशिश करता एक भोला-सा program fragment यह है:
System.out.println(1.03 - 0.42);
दुर्भाग्य से, यह 0.6100000000000001 print करता है। यह कोई अकेला मामला नहीं है। मान लीजिए आपकी जेब में एक डॉलर है और आप दस-दस सेंट के नौ वॉशर ख़रीदते हैं। आपको कितने
पैसे वापस मिलेंगे?
System.out.println(1.00 - 9 * 0.10);
इस program fragment के मुताबिक आपको $0.09999999999999998 वापस मिलते हैं।
आप सोच सकते हैं कि print करने से पहले नतीजों को round करके यह समस्या हल हो जाएगी, लेकिन दुर्भाग्य से यह हमेशा काम नहीं करता। उदाहरण के लिए, मान लीजिए आपकी जेब में एक डॉलर है, और आपको एक शेल्फ़ दिखती है, जिस पर 10¢, 20¢, 30¢ और इसी तरह एक डॉलर तक की कीमत वाली स्वादिष्ट टॉफ़ियों की कतार लगी है। आप 10¢ वाली टॉफ़ी से शुरू करके हर टॉफ़ी में से एक-एक ख़रीदते हैं, तब तक जब तक शेल्फ़ पर अगली टॉफ़ी ख़रीदने लायक पैसे न बचें।
25. Boxed primitives के बजाय primitive types को प्राथमिकता दें
// Broken comparator - can you spot the flaw?
Comparator<Integer> naturalOrder = (i, j) -> (i < j) ? -1 : (i == j ? 0 : 1);
26. Objects को उनके interfaces के ज़रिए refer करें
अच्छा उदाहरण:
Set<Son> sonSet = new LinkedHashSet<>();
बुरा उदाहरण:
LinkedHashSet<Son> sonSet = new LinkedHashSet<>();
27. Reflection के बजाय interfaces को प्राथमिकता दें
वरना:
- आप compile-time type checking के सारे फ़ायदे खो देते हैं, जिसमें exception checking भी शामिल है। अगर कोई program reflection के ज़रिए किसी ऐसे method को invoke करने की कोशिश करता है, जो मौजूद नहीं है या accessible नहीं है, तो वह runtime पर fail होगा – जब तक कि आपने ख़ास सावधानी न बरती हो।
- Reflective access के लिए ज़रूरी कोड भद्दा और लंबा-चौड़ा होता है। इसे लिखना उबाऊ है और पढ़ना मुश्किल।
- Performance पर असर पड़ता है। Reflective method invocation सामान्य method invocation से कहीं धीमा होता है।
28. आम तौर पर स्वीकृत naming conventions का पालन करें
Java platform के पास naming conventions का एक सुस्थापित सेट है, जिनमें से कई The Java Language Specification [JLS, 6.1] में दिए गए हैं।
29. Exceptions का इस्तेमाल सिर्फ़ असाधारण स्थितियों के लिए करें
// Horrible abuse of exceptions. Don't ever do this!
try {
int i = 0;
while (true) {
range[i++].climb();
}
} catch (ArrayIndexOutOfBoundsException e) {
// catch it
}
यह कोड क्या करता है? देखकर यह बिल्कुल भी साफ़ नहीं है, और इसका इस्तेमाल न करने के लिए यही वजह काफ़ी है। दरअसल यह किसी array के elements पर loop चलाने का एक बेहद बेतुका
idiom है। यह infinite loop तब ख़त्म होता है, जब array की सीमा से बाहर के पहले element को access करने की कोशिश पर ArrayIndexOutOfBoundsException throw होता है और उसे catch करके ignore कर दिया
जाता है। इसे array पर loop चलाने के standard idiom के बराबर माना जाता है, जिसे कोई भी Java programmer तुरंत पहचान लेता है:
for (Mountain m : range) {
m.climb();
}
Exceptions, जैसा कि नाम से ही ज़ाहिर है, सिर्फ़ असाधारण स्थितियों के लिए हैं; इनका इस्तेमाल सामान्य control flow के लिए कभी नहीं करना चाहिए।
30. Recoverable स्थितियों के लिए checked exceptions और programming errors के लिए runtime exceptions इस्तेमाल करें
उन स्थितियों के लिए checked exceptions इस्तेमाल करें, जिनसे caller के उबरने की उचित उम्मीद की जा सकती है। User के सामने checked exception रखकर API designer उसे स्थिति से उबरने का निर्देश देता है। User exception को catch करके ignore कर सकता है और इस तरह निर्देश की अनदेखी कर सकता है, लेकिन आम तौर पर यह बुरा विचार है।
Programming errors बताने के लिए runtime exceptions इस्तेमाल करें। ज़्यादातर runtime exceptions precondition violations की ओर इशारा करते हैं। Precondition violation का मतलब बस इतना है कि
API का client, API specification में तय contract का पालन नहीं कर पाया। उदाहरण के लिए, array access का contract कहता है कि array index शून्य और array की
length माइनस एक के बीच (दोनों शामिल) होना चाहिए। ArrayIndexOutOfBoundsException बताता है कि इस precondition का उल्लंघन हुआ है।
31. Checked exceptions के ग़ैर-ज़रूरी इस्तेमाल से बचें
कुल मिलाकर, सोच-समझकर इस्तेमाल किए जाएँ, तो checked exceptions programs की विश्वसनीयता बढ़ा सकते हैं; ज़रूरत से ज़्यादा इस्तेमाल किए जाएँ, तो वे APIs के इस्तेमाल को तकलीफ़देह बना देते हैं।
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. Standard exceptions के इस्तेमाल को प्राथमिकता दें
Expert programmers को कम अनुभवी programmers से अलग करने वाली एक ख़ूबी यह है कि experts कोड को ज़्यादा से ज़्यादा reuse करने की कोशिश करते हैं और आम तौर पर इसमें सफल भी होते हैं।
Exceptions भी इस नियम का अपवाद नहीं हैं कि code reuse अच्छी बात है। Java libraries exceptions का एक ऐसा सेट देती हैं, जो ज़्यादातर APIs की exception throw करने की ज़्यादातर ज़रूरतें पूरी करता है। Standard exceptions को reuse करने के कई फ़ायदे हैं। इनमें सबसे बड़ा यह है कि इससे आपकी API सीखना और इस्तेमाल करना आसान हो जाता है, क्योंकि यह उन स्थापित conventions से मेल खाती है, जिनसे programmers पहले से परिचित हैं। लगभग उतना ही अहम दूसरा फ़ायदा यह है कि आपकी API इस्तेमाल करने वाले programs पढ़ने में आसान होते हैं, क्योंकि वे अनजान exceptions से भरे नहीं होते। आख़िरी (और सबसे कम अहम) बात: कम exception classes का मतलब है कम memory footprint और classes load करने में कम समय।
सबसे ज़्यादा reuse होने वाला exception type IllegalArgumentException है। आम तौर पर यही exception throw करना चाहिए, जब caller कोई ऐसा argument pass करे, जिसकी value अनुचित हो। उदाहरण के लिए,
अगर caller किसी ऐसे parameter में negative number pass करे, जो यह बताता है कि कोई action कितनी बार दोहराया जाए, तो यही exception throw करना चाहिए। एक और आम तौर पर reuse होने वाला
exception IllegalStateException है। आम तौर पर यह exception तब throw किया जाता है, जब receiving object की state की वजह से invocation अवैध हो। उदाहरण के लिए, अगर caller किसी object को
ठीक से initialize होने से पहले ही इस्तेमाल करने की कोशिश करे, तो यही exception throw करना चाहिए।
कहा जा सकता है कि हर गलत method invocation आख़िरकार एक illegal argument या state ही है, लेकिन कुछ ख़ास तरह के illegal arguments और states के लिए दूसरे exceptions standard रूप से इस्तेमाल होते हैं। अगर caller
किसी ऐसे parameter में null pass करता है, जिसमें null values वर्जित हैं, तो convention के मुताबिक IllegalArgumentException के बजाय NullPointerException throw किया जाना चाहिए।
इसी तरह, अगर caller किसी sequence के index को दर्शाने वाले parameter में range से बाहर की value pass करता है, तो IllegalArgumentException के बजाय IndexOutOfBoundsException throw किया जाना चाहिए।
Exception, RuntimeException, Throwable या Error को सीधे reuse न करें। इन classes को ऐसे मानें, मानो ये abstract हों। हालाँकि अगर आप किसी checked exception को runtime exception
में बदलना चाहते हैं और सचमुच जानते हैं कि आप क्या कर रहे हैं, तो RuntimeException इस्तेमाल किया जा सकता है।
33. Exceptions को ignore न करें
// Empty catch block ignores exception - Highly suspect!
try {
...
} catch (SomeException e) {
}
खाली catch block exceptions के मकसद को ही नाकाम कर देता है, जो है आपको असाधारण स्थितियों को handle करने के लिए मजबूर करना। किसी exception को ignore करना fire alarm को ignore करने जैसा है – और उसे बंद भी कर देना, ताकि किसी और को यह देखने का मौका ही न मिले कि सचमुच आग लगी है या नहीं।