“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 वापरा
कधी कधी केवळ instance fields एकत्र ठेवण्यापलीकडे कोणताही उद्देश नसलेल्या, अशा निकृष्ट (degenerate) classes लिहिण्याचा मोह तुम्हाला होऊ शकतो:
// 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 करण्यासाठी हे पाच नियम पाळा:
- Object ची state बदलणाऱ्या methods देऊ नका (त्यांना mutators म्हणतात).
-
Class extend करता येणार नाही याची खात्री करा. यामुळे निष्काळजी किंवा दुष्ट हेतूच्या subclasses, जणू object ची state बदलली आहे असे वागून, class चे immutable वर्तन
बिघडवू शकत नाहीत. Subclassing रोखणे सहसा class
finalकरून साधले जाते. - सर्व fields final करा. यामुळे तुमचा हेतू स्पष्टपणे व्यक्त होतो आणि system स्वतः तो लागू करते. तसेच, नव्याने तयार झालेल्या instance चा reference synchronization शिवाय एका thread कडून दुसऱ्या thread कडे दिला गेला, तर योग्य वर्तनाची खात्री करण्यासाठीही हे आवश्यक आहे, जसे memory model मध्ये स्पष्ट केले आहे.
- सर्व fields private करा. यामुळे fields ज्या mutable objects ना refer करतात, त्यांच्यापर्यंत clients पोहोचू शकत नाहीत आणि ते objects थेट बदलू शकत नाहीत. तांत्रिकदृष्ट्या immutable classes मध्ये primitive values किंवा immutable objects चे references असलेले public final fields ठेवणे ग्राह्य असले, तरी तसे करण्याची शिफारस केली जात नाही, कारण त्यामुळे पुढच्या एखाद्या release मध्ये internal representation बदलणे अशक्य होते.
-
कोणत्याही mutable component चा exclusive access सुनिश्चित करा. तुमच्या class मध्ये mutable objects ना refer करणारे fields असतील, तर 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 च्या नियंत्रणाखाली असतात. Extension साठीच खास design आणि document केलेल्या classes extend करतानाही inheritance सुरक्षित आहे. मात्र package च्या सीमा ओलांडून सामान्य concrete classes पासून inherit करणे धोकादायक आहे.
Method invocation च्या उलट, inheritance encapsulation मोडते. तुम्हाला वाटेल की फक्त नवीन methods जोडल्या आणि अस्तित्वातील methods override केल्या नाहीत, तर class extend करणे सुरक्षित आहे. अशा प्रकारचे extension बरेच अधिक सुरक्षित असले, तरी ते जोखमीपासून पूर्णपणे मुक्त नाही. पुढच्या एखाद्या release मध्ये superclass ला एखादी नवीन 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 ना प्राधान्य द्या
अनेक implementations असू शकणारा type define करण्यासाठी Java मध्ये दोन mechanisms आहेत: 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 म्हणून त्यांच्या वापराला मोठ्या प्रमाणात बांधून ठेवते. सर्व आवश्यक methods define करणारी आणि general contract पाळणारी कोणतीही class interface implement करू शकते – ती class hierarchy मध्ये कुठेही असली तरी. अस्तित्वातील classes मध्ये सहज बदल करून त्यांना नवीन interface implement करायला लावता येते.
- Mixins define करण्यासाठी interfaces आदर्श आहेत.
- 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 करा. कोड typesafe आहे हे आधी सिद्ध न करता तुम्ही warnings suppress केल्या, तर तुम्ही स्वतःला
सुरक्षिततेची खोटी भावना देत आहात.
कोड एकही warning न देता compile होऊ शकतो, पण तरीही runtime ला ClassCastException throw करू शकतो.
प्रत्येक वेळी @SuppressWarnings("unchecked") annotation वापरताना, ते करणे सुरक्षित का आहे हे सांगणारा कमेंट जोडा.
17. Generics: केव्हा योग्य ठरतात
अरेरे – generics ची नितांत गरज असलेली एक class:
असे काहीतरी निदान 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() ने return केलेली 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 द्वारे करा
चांगले उदाहरण:
Set<Son> sonSet = new LinkedHashSet<>();
वाईट उदाहरण:
LinkedHashSet<Son> sonSet = new LinkedHashSet<>();
27. Reflection ऐवजी interfaces ना प्राधान्य द्या
नाहीतर:
- Compile-time type checking चे सर्व फायदे तुम्ही गमावता, exception checking सह. एखादा program reflection द्वारे अस्तित्वात नसलेली किंवा accessible नसलेली method invoke करण्याचा प्रयत्न करत असेल, तर तुम्ही खास काळजी घेतली नसेल तोपर्यंत तो 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 आहे. Array च्या सीमेबाहेरील पहिला element access करण्याचा प्रयत्न होताच ArrayIndexOutOfBoundsException throw होतो आणि तो catch करून ignore केला
जातो, तेव्हा हा infinite loop संपतो. हा array वर loop चालवण्याच्या standard idiom च्या समतुल्य असणे अपेक्षित आहे, जो कोणताही Java programmer लगेच ओळखतो:
for (Mountain m : range) {
m.climb();
}
नावाप्रमाणेच, exceptions फक्त अपवादात्मक परिस्थितींसाठीच वापरायचे असतात; सामान्य control flow साठी ते कधीही वापरू नयेत.
30. Recoverable परिस्थितींसाठी checked exceptions आणि programming errors साठी runtime exceptions वापरा
ज्या परिस्थितीतून caller सावरेल अशी वाजवी अपेक्षा करता येते, त्यांच्यासाठी checked exceptions वापरा. 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 वापरण्याला प्राधान्य द्या
तज्ज्ञ programmers ना कमी अनुभवी programmers पासून वेगळे करणारे एक वैशिष्ट्य म्हणजे तज्ज्ञ code reuse चे उच्च प्रमाण गाठण्याचा प्रयत्न करतात आणि सहसा ते गाठतातही.
Code reuse ही चांगली गोष्ट आहे, या नियमाला exceptions सुद्धा अपवाद नाहीत. Java libraries exceptions चा असा संच पुरवतात, जो बहुतेक APIs च्या exception throw करण्याच्या बहुतेक गरजा भागवतो. Standard exceptions reuse करण्याचे अनेक फायदे आहेत. त्यातील प्रमुख फायदा म्हणजे तुमची API शिकणे आणि वापरणे सोपे होते, कारण ती programmers ना आधीच परिचित असलेल्या प्रस्थापित conventions शी जुळते. जवळजवळ तितकाच महत्त्वाचा दुसरा फायदा म्हणजे तुमची API वापरणारे programs वाचायला सोपे असतात, कारण ते अपरिचित exceptions नी भरलेले नसतात. शेवटचा (आणि सर्वात कमी महत्त्वाचा) फायदा: कमी exception classes म्हणजे कमी memory footprint आणि classes load करण्यात कमी वेळ.
सर्वाधिक reuse होणारा exception type म्हणजे IllegalArgumentException. caller ने अयोग्य value असलेला argument pass केला, तर सहसा हाच exception throw करावा. उदाहरणार्थ,
एखादी action किती वेळा पुन्हा करायची हे दर्शवणाऱ्या parameter मध्ये caller ने negative number pass केला, तर हाच exception throw करायला हवा. आणखी एक नेहमी reuse होणारा
exception म्हणजे IllegalStateException. Receiving object च्या state मुळे invocation अवैध असेल, तर सहसा हा exception throw केला जातो. उदाहरणार्थ, एखादा object
योग्य प्रकारे initialize होण्यापूर्वीच caller ने तो वापरण्याचा प्रयत्न केला, तर हाच exception throw करायला हवा.
असे म्हणता येईल की प्रत्येक चुकीचे method invocation शेवटी illegal argument किंवा state मध्येच मोडते, पण विशिष्ट प्रकारच्या illegal arguments आणि states साठी इतर exceptions standard म्हणून वापरले जातात. caller ने
ज्या parameter मध्ये null values निषिद्ध आहेत, त्यात null pass केले, तर convention नुसार IllegalArgumentException ऐवजी NullPointerException throw करावा.
त्याचप्रमाणे, एखाद्या sequence मधील index दर्शवणाऱ्या parameter मध्ये caller ने range बाहेरची value pass केली, तर IllegalArgumentException ऐवजी IndexOutOfBoundsException throw करावा.
Exception, RuntimeException, Throwable किंवा Error थेट reuse करू नका. या classes जणू abstract आहेत असे समजून वागा. मात्र एखादा checked exception runtime exception
मध्ये बदलायचा असेल आणि तुम्ही काय करत आहात हे तुम्हाला खरोखर माहीत असेल, तर RuntimeException वापरता येतो.
33. Exceptions कडे दुर्लक्ष करू नका
// Empty catch block ignores exception - Highly suspect!
try {
...
} catch (SomeException e) {
}
रिकामा catch block exceptions चा मूळ उद्देशच निष्फळ करतो, आणि तो उद्देश म्हणजे तुम्हाला अपवादात्मक परिस्थिती handle करायला भाग पाडणे. एखाद्या exception कडे दुर्लक्ष करणे म्हणजे fire alarm कडे दुर्लक्ष करण्यासारखे आहे – आणि तो बंदही करून टाकणे, जेणेकरून खरोखर आग लागली आहे का हे पाहण्याची संधी इतर कोणालाही मिळू नये.