“Effective Java (3rd Edition)” நூலிலிருந்து Best Practices
Joshua Bloch எழுதிய “Effective Java (3rd Edition) ” நூலிலிருந்து சில பகுதிகள்
1. null அல்ல, காலியான collection அல்லது array-ஐ return செய்யுங்கள்
Method ஒரு Set-ஐ return செய்தால், காலியான முடிவுக்கு return null; வேண்டாம் – அதற்குப் பதிலாக return Set.of();.
2. Constructor-களுக்குப் பதிலாக static factory method-களைக் கருத்தில் கொள்ளுங்கள்
public static Boolean valueOf(boolean b) {
return b ? Boolean.TRUE : Boolean.FALSE;
}
எப்போதும் சிறந்தது அல்ல, ஆனால் சில பயன்பாடுகளில் இவை பல நன்மைகளைத் தருகின்றன:
- Static factory method-களின் ஒரு நன்மை: constructor-களைப் போலல்லாமல், இவற்றுக்குப் பெயர்கள் உண்டு.
- ஒவ்வொரு முறை அழைக்கப்படும்போதும் இவை புதிய object-ஐ உருவாக்க வேண்டியதில்லை.
- இவை தங்கள் return type-இன் எந்த subtype-இன் object-ஐயும் return செய்யலாம்.
- Input parameter-களைப் பொறுத்து, return செய்யப்படும் object-இன் class ஒவ்வொரு அழைப்பிலும் மாறலாம்.
- Method-ஐக் கொண்டுள்ள class எழுதப்படும்போது, return செய்யப்படும் object-இன் class இருக்க வேண்டிய அவசியம்கூட இல்லை.
குறைபாடுகள்:
- Static factory method-களை மட்டுமே வழங்குவதன் முக்கியக் கட்டுப்பாடு: public அல்லது protected constructor இல்லாத class-களை subclass செய்ய முடியாது.
- Static factory method-களின் இரண்டாவது குறைபாடு: programmer-களுக்கு இவற்றைக் கண்டுபிடிப்பது கடினம்.
3. Constructor parameter-கள் அதிகமாக இருக்கும்போது builder-ஐக் கருத்தில் கொள்ளுங்கள்
Static factory-களுக்கும் constructor-களுக்கும் ஒரு பொதுவான கட்டுப்பாடு உண்டு: அதிக எண்ணிக்கையிலான optional parameter-களுக்கு அவை நன்றாக ஈடுகொடுப்பதில்லை. நான் பணியாற்றிய சில project-களை இது மிகக் கடுமையாகப் பாதித்தது.
4. Private constructor அல்லது enum type மூலம் singleton பண்பை உறுதிசெய்யுங்கள்
பல instance-கள் தேவைப்படாத எதுவும் ஒரே ஒருமுறை மட்டுமே instantiate செய்யப்பட வேண்டும் – @ApplicationScoped மூலமாகவோ வேறொரு singleton pattern மூலமாகவோ.
5. Private constructor மூலம் instantiation-ஐத் தடுங்கள்
சில நேரங்களில், static method-களும் static field-களும் மட்டுமே கொண்ட ஒரு class-ஐ எழுத விரும்புவீர்கள். Object-களின் அடிப்படையில் சிந்திப்பதைத் தவிர்க்க சிலர் இவற்றைத் தவறாகப் பயன்படுத்துவதால் இத்தகைய class-களுக்குக் கெட்ட பெயர் உண்டு; ஆனால் இவற்றுக்கு நியாயமான பயன்பாடுகளும் உள்ளன.
private MyStaticClass() {
// private constructor to prevent instantiation
}
6. தேவையற்ற object-களை உருவாக்குவதைத் தவிர்த்திடுங்கள்
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 சரியான பதிலைத் தருகிறது, ஆனால் ஒரே ஓர் எழுத்துப் பிழையின் காரணமாக இருக்க வேண்டியதை விட மிகவும் மெதுவாக இயங்குகிறது. sum என்ற variable, long-க்குப் பதிலாக Long என அறிவிக்கப்பட்டுள்ளது; இதனால் program சுமார் 2^31 தேவையற்ற Long instance-களை உருவாக்குகிறது (தோராயமாக, long i ஒவ்வொரு முறை Long sum-உடன் கூட்டப்படும்போதும் ஒன்று).
7. Finalizer-களையும் cleaner-களையும் தவிர்த்திடுங்கள்
Finalizer-கள் கணிக்க முடியாதவை, பெரும்பாலும் ஆபத்தானவை, பொதுவாகத் தேவையற்றவை. அவற்றைப் பயன்படுத்துவது ஒழுங்கற்ற நடத்தை, மோசமான 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 செய்வதன் மூலம், இந்த interface-ஐச் சார்ந்திருக்கும் ஏராளமான generic algorithm-களுடனும் collection implementation-களுடனும் உங்கள் class இணைந்து செயல்பட முடிகிறது.
10. Class-கள், member-களின் அணுகலை (accessibility) முடிந்தவரை குறையுங்கள்
நன்கு வடிவமைக்கப்பட்ட ஒரு component-ஐ மோசமாக வடிவமைக்கப்பட்ட ஒன்றிலிருந்து வேறுபடுத்தும் மிக முக்கியமான காரணி, அந்த component தன் உள் data-வையும் பிற implementation விவரங்களையும் பிற component-களிடமிருந்து எந்த அளவுக்கு மறைக்கிறது என்பதே. நன்கு வடிவமைக்கப்பட்ட component தன் implementation விவரங்கள் அனைத்தையும் மறைத்து, தன் API-ஐ implementation-இலிருந்து தெளிவாகப் பிரிக்கிறது. அதன்பின் component-கள் தங்கள் API-கள் வழியாக மட்டுமே தொடர்புகொள்கின்றன; ஒன்றின் உள் செயல்பாடுகள் பற்றி மற்றொன்றுக்கு எதுவும் தெரியாது. Information hiding அல்லது encapsulation எனப்படும் இந்தக் கருத்து, மென்பொருள் வடிவமைப்பின் அடிப்படைக் கோட்பாடுகளில் ஒன்று.
11. Public class-களில், public field-களை அல்ல, accessor method-களைப் பயன்படுத்துங்கள்
சில நேரங்களில், instance field-களைக் குழுவாக்குவதைத் தவிர வேறு எந்த நோக்கமும் இல்லாத, சீரழிந்த (degenerate) class-களை எழுத ஆசை வரலாம்:
// Degenerate classes like this should not be public!
class Point {
public double x;
public double y;
}
இத்தகைய class-களின் data field-கள் நேரடியாக அணுகப்படுவதால், இவை encapsulation-இன் நன்மைகளைத் தருவதில்லை. API-ஐ மாற்றாமல் representation-ஐ மாற்ற முடியாது, invariant-களை உறுதிசெய்ய முடியாது, ஒரு field அணுகப்படும்போது கூடுதல் நடவடிக்கை எதுவும் எடுக்கவும் முடியாது. கடும்போக்கு object-oriented programmer-கள், இத்தகைய class-கள் முற்றிலும் வெறுக்கத்தக்கவை என்றும், அவற்றை எப்போதும் private field-களும் public accessor method-களும் (getter-கள்), mutable class-களுக்கு mutator-களும் (setter-கள்) கொண்ட class-களால் மாற்ற வேண்டும் என்றும் கருதுகிறார்கள்:
// 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 class-களைப் பொறுத்தவரை கடும்போக்காளர்கள் சொல்வது நிச்சயமாகச் சரி: ஒரு class அதன் package-க்கு வெளியே அணுகக்கூடியதாக இருந்தால், class-இன் உள் representation-ஐ மாற்றும் நெகிழ்வுத்தன்மையைத் தக்கவைக்க accessor method-களை வழங்குங்கள். ஒரு public class தன் data field-களை வெளிப்படுத்தினால், client code எங்கெங்கும் பரவியிருக்கக்கூடும் என்பதால், அதன் representation-ஐ மாற்றும் நம்பிக்கை முழுவதும் போய்விடும்.
ஆனால் ஒரு class package-private ஆகவோ private nested class ஆகவோ இருந்தால், அதன் data field-களை வெளிப்படுத்துவதில் அடிப்படையில் தவறு எதுவும் இல்லை – அந்த field-கள் class வழங்கும் abstraction-ஐப் போதுமான அளவு விவரிக்கும் பட்சத்தில்.
12. Mutability-ஐ முடிந்தவரை குறையுங்கள்
Immutable class என்பது, அதன் instance-களை மாற்ற முடியாத ஒரு class. ஒவ்வொரு instance-இலும் உள்ள தகவல் அனைத்தும் object-இன் வாழ்நாள் முழுவதும் நிலையாக இருக்கும்; எனவே எந்த மாற்றத்தையும் ஒருபோதும் காண முடியாது. Java platform library-களில் பல immutable class-கள் உள்ளன: String, boxed primitive class-கள், BigInteger, BigDecimal போன்றவை. இதற்குப் பல நல்ல காரணங்கள் உண்டு: immutable class-களை mutable class-களை விட வடிவமைப்பதும், implement செய்வதும், பயன்படுத்துவதும் எளிது. அவற்றில் பிழைகளுக்கு வாய்ப்பு குறைவு, அவை அதிகப் பாதுகாப்பானவை. ஒரு class-ஐ immutable ஆக்க, இந்த ஐந்து விதிகளைப் பின்பற்றுங்கள்:
- Object-இன் state-ஐ மாற்றும் method-களை வழங்காதீர்கள் (இவை mutator-கள் எனப்படும்).
-
Class-ஐ extend செய்ய முடியாதபடி உறுதிசெய்யுங்கள். Object-இன் state மாறிவிட்டதுபோல் நடந்துகொண்டு, கவனக்குறைவான அல்லது தீங்கிழைக்கும் subclass-கள் class-இன் immutable
நடத்தையைக் கெடுப்பதை இது தடுக்கிறது. Subclass உருவாக்கத்தைத் தடுப்பது பொதுவாக, எடுத்துக்காட்டாக, class-ஐ
finalஆக்குவதன் மூலம் செய்யப்படுகிறது. - எல்லா field-களையும் final ஆக்குங்கள். System தானே அமல்படுத்தும் வகையில் இது உங்கள் நோக்கத்தைத் தெளிவாக வெளிப்படுத்துகிறது. மேலும், memory model-இல் விவரிக்கப்பட்டுள்ளபடி, புதிதாக உருவாக்கப்பட்ட ஒரு instance-இன் reference, synchronization இல்லாமல் ஒரு thread-இலிருந்து இன்னொன்றுக்கு அனுப்பப்பட்டால் சரியான நடத்தையை உறுதிசெய்யவும் இது அவசியம்.
- எல்லா field-களையும் private ஆக்குங்கள். Field-கள் குறிப்பிடும் mutable object-களை client-கள் அணுகி நேரடியாக மாற்றுவதை இது தடுக்கிறது. Primitive மதிப்புகளையோ immutable object-களுக்கான reference-களையோ கொண்ட public final field-களை immutable class-கள் வைத்திருப்பது தொழில்நுட்ப ரீதியாக அனுமதிக்கப்பட்டாலும், அது பரிந்துரைக்கப்படுவதில்லை; ஏனெனில் பிந்தைய release-இல் உள் representation-ஐ மாற்றுவதை அது தடுத்துவிடுகிறது.
-
எந்த mutable component-க்கும் பிரத்யேக அணுகலை உறுதிசெய்யுங்கள். உங்கள் class-இல் mutable object-களைக் குறிப்பிடும் field-கள் இருந்தால், class-இன் client-கள் அந்த
object-களுக்கான reference-களைப் பெற முடியாதபடி உறுதிசெய்யுங்கள். அத்தகைய field-ஐ client வழங்கிய object reference கொண்டு ஒருபோதும் initialize செய்யாதீர்கள்; accessor-இலிருந்து அந்த field-ஐ return
செய்யவும் வேண்டாம். Constructor-கள், accessor-கள்,
readObjectmethod-கள் ஆகியவற்றில் defensive copy-களை உருவாக்குங்கள்.
13. Inheritance-ஐ விட composition-க்கு முன்னுரிமை கொடுங்கள்
Code-ஐ மறுபயன்பாடு செய்ய inheritance ஒரு வலிமையான வழி, ஆனால் அது எப்போதும் அந்த வேலைக்குச் சிறந்த கருவி அல்ல. தவறாகப் பயன்படுத்தினால், அது எளிதில் உடையக்கூடிய மென்பொருளை உருவாக்குகிறது. Subclass, superclass implementation-கள் ஒரே programmer-களின் கட்டுப்பாட்டில் இருக்கும் ஒரு package-க்குள் inheritance-ஐப் பயன்படுத்துவது பாதுகாப்பானது. Extend செய்வதற்காகவே வடிவமைக்கப்பட்டு ஆவணப்படுத்தப்பட்ட class-களை extend செய்யும்போதும் inheritance பாதுகாப்பானது. ஆனால் package எல்லைகளைக் கடந்து சாதாரண concrete class-களிலிருந்து inherit செய்வது ஆபத்தானது.
Method அழைப்பைப் போலல்லாமல், inheritance encapsulation-ஐ மீறுகிறது. புதிய method-களை மட்டும் சேர்த்து, ஏற்கெனவே உள்ள method-களை override செய்யாமல் இருந்தால், ஒரு class-ஐ extend செய்வது பாதுகாப்பானது என்று நீங்கள் நினைக்கலாம். இத்தகைய extension மிகவும் பாதுகாப்பானது என்றாலும், அபாயமற்றது அல்ல. அடுத்த release-இல் superclass-க்கு ஒரு புதிய method கிடைத்து, துரதிர்ஷ்டவசமாக நீங்கள் subclass-இல் அதே signature-உம் வேறு return type-உம் கொண்ட ஒரு method-ஐ வைத்திருந்தால், உங்கள் subclass இனி compile ஆகாது. புதிய superclass method-இன் அதே signature-உம் return type-உம் கொண்ட method-ஐ subclass-இல் வைத்திருந்தால், இப்போது நீங்கள் அதை override செய்கிறீர்கள்; எனவே முன்பு விவரித்த சிக்கல்களுக்கு ஆளாவீர்கள். மேலும், உங்கள் method புதிய superclass method-இன் contract-ஐ நிறைவேற்றுமா என்பது சந்தேகமே; ஏனெனில் நீங்கள் subclass method-ஐ எழுதியபோது அந்த contract இன்னும் எழுதப்படவில்லை.
அதிர்ஷ்டவசமாக, மேலே விவரித்த எல்லாச் சிக்கல்களையும் தவிர்க்க ஒரு வழி உண்டு. ஏற்கெனவே உள்ள class-ஐ extend செய்வதற்குப் பதிலாக, அந்த class-இன் ஒரு instance-ஐக் குறிப்பிடும் ஒரு private field-ஐ உங்கள் புதிய class-க்குக் கொடுங்கள். இருக்கும் class புதிய class-இன் ஒரு component ஆகிவிடுவதால், இந்த வடிவமைப்பு composition எனப்படுகிறது. புதிய class-இன் ஒவ்வொரு instance method-உம், உள்ளே இருக்கும் பழைய class instance-இன் தொடர்புடைய method-ஐ அழைத்து, முடிவுகளை return செய்கிறது. இது forwarding எனப்படுகிறது; புதிய class-இன் method-கள் forwarding method-கள் எனப்படுகின்றன. இதன் விளைவாகக் கிடைக்கும் class பாறைபோல் உறுதியானது; பழைய class-இன் implementation விவரங்களை அது சார்ந்திருப்பதில்லை. பழைய class-இல் புதிய method-களைச் சேர்த்தாலும்கூட புதிய class-இல் எந்தத் தாக்கமும் இருக்காது.
14. Abstract class-களை விட interface-களுக்கு முன்னுரிமை கொடுங்கள்
பல implementation-களை அனுமதிக்கும் ஒரு type-ஐ வரையறுக்க Java-வில் இரண்டு வழிமுறைகள் உள்ளன: interface-களும் abstract class-களும். Java 8-இல் interface-களுக்கு default method-கள் அறிமுகமானதிலிருந்து, இரண்டு வழிமுறைகளிலும் சில instance method-களுக்கு implementation வழங்க முடியும்.
ஒரு முக்கிய வேறுபாடு: abstract class வரையறுக்கும் type-ஐ implement செய்ய, ஒரு class அந்த abstract class-இன் subclass ஆக இருக்க வேண்டும். Java ஒற்றை inheritance-ஐ மட்டுமே அனுமதிப்பதால், abstract class-களின் இந்தக் கட்டுப்பாடு type வரையறைகளாக அவற்றின் பயன்பாட்டைக் கடுமையாக வரம்புக்குள்ளாக்குகிறது. தேவையான எல்லா method-களையும் வரையறுத்து general contract-ஐப் பின்பற்றும் எந்த class-உம், class hierarchy-இல் எங்கிருந்தாலும், ஒரு interface-ஐ implement செய்யலாம். ஏற்கெனவே உள்ள class-களில் ஒரு புதிய interface-ஐ எளிதாகப் பொருத்தி implement செய்யலாம்.
- Mixin-களை வரையறுக்க interface-கள் மிகப் பொருத்தமானவை.
- படிநிலையற்ற (nonhierarchical) type framework-களை உருவாக்க interface-கள் உதவுகின்றன.
- Interface-கள் பாதுகாப்பான, வலிமையான functionality மேம்பாடுகளைச் சாத்தியமாக்குகின்றன – wrapper class idiom மூலம். Type-களை வரையறுக்க abstract class-களைப் பயன்படுத்தினால், functionality-ஐச் சேர்க்க விரும்பும் programmer-க்கு inheritance-ஐத் தவிர வேறு வழியே இருக்காது.
15. Generics: raw type-களைப் பயன்படுத்தாதீர்கள்
பிழை நடந்து நீண்ட நேரம் கழித்து, runtime-இல்தான் அதைக் கண்டுபிடிப்பீர்கள் – அதுவும் பிழை உள்ள code-இலிருந்து வெகு தொலைவில் இருக்கக்கூடிய code-இல். ClassCastException-ஐப் பார்த்தவுடன், stamp சேகரிப்புக்குள் நாணயத்தைப் போட்ட அந்த method அழைப்பைத் தேடி codebase முழுவதையும் அலச வேண்டியிருக்கும். Compiler-ஆல் உங்களுக்கு உதவ முடியாது.
16. Generics: unchecked warning-களை நீக்குங்கள்
Generics கொண்டு program எழுதும்போது, பல compiler warning-களைக் காண்பீர்கள்:
- unchecked cast warning-கள்
- unchecked method invocation warning-கள்
- unchecked parameterized vararg type warning-கள்
- unchecked conversion warning-கள்.
Generics-இல் உங்கள் அனுபவம் கூடக்கூட warning-கள் குறையும்; ஆனால் புதிதாக எழுதிய code சுத்தமாக compile ஆகும் என்று எதிர்பார்க்காதீர்கள். பல unchecked warning-களை எளிதாக நீக்கலாம். எடுத்துக்காட்டாக, தவறுதலாக இந்த declaration-ஐ எழுதிவிட்டீர்கள் என்று வைத்துக்கொள்ளுங்கள்:
Set<Lark> exaltation = new HashSet();
இதை இப்படி மாற்றுங்கள்:
Set<Lark> exaltation = new HashSet<>();
சில warning-களை நீக்குவது மிகவும் கடினமாக இருக்கும். கொஞ்சம் யோசிக்க வேண்டிய warning-கள் வரும்போது, விடாமுயற்சியுடன் இருங்கள்! உங்களால் முடிந்த ஒவ்வொரு unchecked warning-ஐயும் நீக்குங்கள்.
எல்லா warning-களையும் நீக்கினால், உங்கள் code typesafe என்பது உறுதி – இது மிக நல்ல விஷயம். Runtime-இல் ClassCastException வராது என்பது இதன் பொருள்; உங்கள் program நீங்கள் நினைத்தபடியே
நடந்துகொள்ளும் என்ற நம்பிக்கையையும் இது அதிகரிக்கிறது. ஒரு warning-ஐ நீக்க முடியாவிட்டாலும், அதைத் தூண்டிய code typesafe என்று நிரூபிக்க முடிந்தால், அப்போது (அப்போது மட்டுமே)
@SuppressWarnings("unchecked") annotation மூலம் அந்த warning-ஐ அடக்குங்கள். Code typesafe என்று முதலில் நிரூபிக்காமல் warning-களை அடக்கினால், உங்களுக்கு நீங்களே
ஒரு போலியான பாதுகாப்பு உணர்வைத் தருகிறீர்கள்.
Code எந்த warning-உம் இல்லாமல் compile ஆகலாம், ஆனாலும் runtime-இல் ClassCastException-ஐ வீசலாம்.
ஒவ்வொரு முறை @SuppressWarnings("unchecked") annotation-ஐப் பயன்படுத்தும்போதும், அது ஏன் பாதுகாப்பானது என்று ஒரு comment சேருங்கள்.
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. Array-களை விட list-களுக்கு முன்னுரிமை கொடுங்கள்
Array-கள் இரண்டு முக்கிய வழிகளில் generic type-களிலிருந்து வேறுபடுகின்றன. முதலாவதாக, array-கள் covariant. பயமுறுத்தும் இந்தச் சொல்லின் பொருள் எளிமையானது: Sub என்பது Super-இன் subtype என்றால்,
array type Sub[], array type Super[]-இன் subtype. மாறாக, generics invariant: எந்த இரண்டு வெவ்வேறு type-கள் Type1, Type2-க்கும்,
List<Type1> என்பது List<Type2>-இன் subtype-உம் அல்ல, supertype-உம் அல்ல.
20. Anonymous class-களை விட lambda-க்களுக்கு முன்னுரிமை கொடுங்கள்
// 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 variable-களின் scope-ஐ முடிந்தவரை குறையுங்கள்
ஒரு local variable-இன் scope-ஐக் குறைப்பதற்கான மிக வலிமையான உத்தி, அது முதலில் பயன்படுத்தப்படும் இடத்திலேயே அதை அறிவிப்பது...
22. பாரம்பரிய for loop-களை விட for-each loop-களுக்கு முன்னுரிமை கொடுங்கள்
// The preferred idiom for iterating over collections and arrays
for (Element e : elements) {
... // Do something with e
}
23. Library-களை அறிந்து பயன்படுத்துங்கள்
பூஜ்ஜியத்துக்கும் ஒரு மேல் வரம்புக்கும் இடையே random integer-களை உருவாக்க விரும்புகிறீர்கள் என்று வைத்துக்கொள்ளுங்கள். இந்தப் பொதுவான பணியை எதிர்கொள்ளும் பல programmer-கள் கிட்டத்தட்ட இப்படி ஒரு சிறிய method-ஐ எழுதுவார்கள்:
// Common but deeply flawed!
static Random rnd = new Random();
static int random(int n) {
return Math.abs(rnd.nextInt()) % n;
}
இந்த method நன்றாகத் தோன்றலாம், ஆனால் அதில் மூன்று குறைபாடுகள் உள்ளன. முதலாவது: n இரண்டின் ஒரு சிறிய அடுக்காக (power of two) இருந்தால், random எண்களின் வரிசை சற்றுக் குறுகிய காலத்திலேயே மீண்டும் வரத் தொடங்கும். இரண்டாவது குறைபாடு: n இரண்டின் அடுக்காக இல்லாவிட்டால், சராசரியாக சில எண்கள் மற்றவற்றை விட அடிக்கடி return செய்யப்படும். n பெரிதாக இருந்தால், இந்த விளைவு மிகத் தெளிவாகத் தெரியலாம்.
random method-இன் மூன்றாவது குறைபாடு: அரிதான சந்தர்ப்பங்களில் அது பேரழிவுகரமாகத் தோல்வியடைந்து, குறிப்பிட்ட வரம்புக்கு வெளியே உள்ள ஓர் எண்ணை return செய்யலாம். ஏனெனில்,
rnd.nextInt() return செய்யும் மதிப்பை, Math.abs-ஐ அழைத்து எதிர்மறையல்லாத ஒரு int-ஆக மாற்ற method முயல்கிறது. nextInt()
Integer.MIN_VALUE-ஐ return செய்தால், Math.abs-உம் Integer.MIN_VALUE-ஐயே return செய்யும்; n இரண்டின் அடுக்காக இல்லை என்றால், மீதி operator
(%) ஓர் எதிர்மறை எண்ணை return செய்யும். இது கிட்டத்தட்ட நிச்சயமாக உங்கள் program-ஐத் தோல்வியடையச் செய்யும்; அந்தத் தோல்வியை மீண்டும் உருவாக்குவதும் கடினமாக இருக்கலாம்.
24. துல்லியமான பதில்கள் தேவைப்படும்போது float, double-ஐத் தவிர்த்திடுங்கள்
பணத் தொகைகளுக்கு BigDecimal-ஐப் பயன்படுத்துங்கள். float, double ஆகியவை rounding பிழைகளை உண்டாக்குகின்றன.
எடுத்துக்காட்டாக, உங்கள் பையில் $1.03 இருக்கிறது, நீங்கள் 42¢ செலவழிக்கிறீர்கள் என்று வைத்துக்கொள்ளுங்கள். உங்களிடம் எவ்வளவு பணம் மீதமிருக்கும்? இந்தக் கேள்விக்குப் பதிலளிக்க முயலும் ஓர் அப்பாவியான program துண்டு இதோ:
System.out.println(1.03 - 0.42);
துரதிர்ஷ்டவசமாக, இது 0.6100000000000001 என்று print செய்கிறது. இது தனிப்பட்ட ஒரு நிகழ்வு அல்ல. உங்கள் பையில் ஒரு டாலர் இருக்கிறது, ஒவ்வொன்றும் பத்து சென்ட் விலையுள்ள ஒன்பது washer-களை வாங்குகிறீர்கள்
என்று வைத்துக்கொள்ளுங்கள். உங்களுக்கு எவ்வளவு மீதிச் சில்லறை கிடைக்கும்?
System.out.println(1.00 - 9 * 0.10);
இந்த program துண்டின்படி, உங்களுக்குக் கிடைப்பது $0.09999999999999998.
Print செய்வதற்கு முன் முடிவுகளை round செய்தாலே சிக்கல் தீர்ந்துவிடும் என்று நீங்கள் நினைக்கலாம்; ஆனால் துரதிர்ஷ்டவசமாக, அது எப்போதும் வேலை செய்யாது. எடுத்துக்காட்டாக, உங்கள் பையில் ஒரு டாலர் இருக்கிறது; ஓர் அலமாரியில் 10¢, 20¢, 30¢ என ஒரு டாலர் வரை விலையுள்ள சுவையான மிட்டாய்கள் வரிசையாக இருக்கின்றன. 10¢ மிட்டாயிலிருந்து தொடங்கி, அலமாரியில் அடுத்த மிட்டாயை வாங்க முடியாத வரை, ஒவ்வொன்றிலும் ஒன்றை வாங்குகிறீர்கள்.
25. Boxed primitive-களை விட primitive type-களுக்கு முன்னுரிமை கொடுங்கள்
// Broken comparator - can you spot the flaw?
Comparator<Integer> naturalOrder = (i, j) -> (i < j) ? -1 : (i == j ? 0 : 1);
26. Object-களை அவற்றின் interface-கள் மூலம் குறிப்பிடுங்கள்
நல்ல எடுத்துக்காட்டு:
Set<Son> sonSet = new LinkedHashSet<>();
மோசமான எடுத்துக்காட்டு:
LinkedHashSet<Son> sonSet = new LinkedHashSet<>();
27. Reflection-ஐ விட interface-களுக்கு முன்னுரிமை கொடுங்கள்
இல்லையென்றால்:
- Compile-time type checking-இன் எல்லா நன்மைகளையும் இழப்பீர்கள், exception checking உட்பட. இல்லாத அல்லது அணுக முடியாத ஒரு method-ஐ reflection மூலம் அழைக்க ஒரு program முயன்றால், நீங்கள் சிறப்பு முன்னெச்சரிக்கைகள் எடுத்திருந்தால் ஒழிய, அது runtime-இல் தோல்வியடையும்.
- Reflection மூலம் அணுகுவதற்குத் தேவையான code சிக்கலானது, நீளமானது. எழுதுவது சலிப்பானது, படிப்பது கடினம்.
- Performance பாதிக்கப்படுகிறது. Reflection மூலம் method அழைப்பது, சாதாரண method அழைப்பை விட மிகவும் மெதுவானது.
28. பொதுவாக ஏற்றுக்கொள்ளப்பட்ட naming convention-களைப் பின்பற்றுங்கள்
Java platform-க்கு நன்கு நிலைபெற்ற naming convention-களின் தொகுப்பு ஒன்று உள்ளது; அவற்றில் பல The Java Language Specification [JLS, 6.1]-இல் இடம்பெற்றுள்ளன.
29. Exception-களை விதிவிலக்கான சூழல்களுக்கு மட்டுமே பயன்படுத்துங்கள்
// Horrible abuse of exceptions. Don't ever do this!
try {
int i = 0;
while (true) {
range[i++].climb();
}
} catch (ArrayIndexOutOfBoundsException e) {
// catch it
}
இந்த code என்ன செய்கிறது? பார்த்தவுடன் அது சிறிதும் தெளிவாகத் தெரியவில்லை; அதைப் பயன்படுத்தாமல் இருக்க அதுவே போதுமான காரணம். உண்மையில், ஒரு array-இன் உறுப்புகள் வழியாக loop செய்வதற்கான,
மிக மோசமாகச் சிந்திக்கப்பட்ட idiom இது. Array-இன் எல்லைக்கு வெளியே உள்ள முதல் உறுப்பை அணுக முயலும்போது, ஒரு ArrayIndexOutOfBoundsException-ஐ வீசி, பிடித்து, புறக்கணிப்பதன் மூலம்
இந்த முடிவற்ற loop முடிகிறது. Array வழியாக loop செய்வதற்கான standard idiom-க்கு இது சமமானதாக இருக்க வேண்டும்; அந்த idiom-ஐ எந்த Java programmer-உம் உடனே அடையாளம் கண்டுகொள்வார்:
for (Mountain m : range) {
m.climb();
}
பெயரே சொல்வதுபோல, exception-கள் விதிவிலக்கான சூழல்களுக்கு மட்டுமே; சாதாரண control flow-க்கு அவற்றை ஒருபோதும் பயன்படுத்தக் கூடாது.
30. மீளக்கூடிய சூழல்களுக்கு checked exception-களையும், programming பிழைகளுக்கு runtime exception-களையும் பயன்படுத்துங்கள்
அழைப்பவர் நியாயமாக மீண்டுவர முடியும் என்று எதிர்பார்க்கக்கூடிய சூழல்களுக்கு checked exception-களைப் பயன்படுத்துங்கள். ஒரு checked exception-ஐப் பயனரின் முன் வைப்பதன் மூலம், API வடிவமைப்பாளர் அந்தச் சூழலிலிருந்து மீண்டுவர வேண்டும் என்ற கட்டளையை முன்வைக்கிறார். Exception-ஐப் பிடித்துப் புறக்கணிப்பதன் மூலம் பயனர் அந்தக் கட்டளையை அலட்சியம் செய்யலாம், ஆனால் அது பொதுவாக மோசமான யோசனை.
Programming பிழைகளைக் குறிக்க runtime exception-களைப் பயன்படுத்துங்கள். பெரும்பாலான runtime exception-கள் precondition மீறல்களைக் குறிக்கின்றன. Precondition மீறல் என்பது, API
specification நிர்ணயித்த contract-ஐ ஒரு API-இன் client பின்பற்றத் தவறுவதுதான். எடுத்துக்காட்டாக, array அணுகலுக்கான contract, array index பூஜ்ஜியத்துக்கும் array நீளம் கழித்தல் ஒன்றுக்கும்
இடையே (இரண்டும் உட்பட) இருக்க வேண்டும் என்று கூறுகிறது. இந்த precondition மீறப்பட்டதை ArrayIndexOutOfBoundsException குறிக்கிறது.
31. Checked exception-களைத் தேவையில்லாமல் பயன்படுத்துவதைத் தவிர்த்திடுங்கள்
சுருக்கமாக, அளவோடு பயன்படுத்தினால் checked exception-கள் program-களின் நம்பகத்தன்மையை அதிகரிக்கலாம்; அளவுக்கு மீறிப் பயன்படுத்தினால், API-களைப் பயன்படுத்துவது வேதனையாகிவிடும்.
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 exception-களைப் பயன்படுத்துவதற்கு முன்னுரிமை கொடுங்கள்
நிபுணத்துவம் வாய்ந்த programmer-களை அனுபவம் குறைந்தவர்களிடமிருந்து வேறுபடுத்தும் ஒரு பண்பு: நிபுணர்கள் அதிக அளவு code மறுபயன்பாட்டுக்கு முயல்கிறார்கள், பொதுவாக அதை அடைகிறார்கள்.
Code மறுபயன்பாடு நல்லது என்ற விதிக்கு exception-களும் விதிவிலக்கல்ல. பெரும்பாலான API-களின் exception வீசும் தேவைகளில் பெரும்பாலானவற்றை ஈடுசெய்யும் exception-களின் தொகுப்பை Java library-கள் வழங்குகின்றன. Standard exception-களை மறுபயன்பாடு செய்வதில் பல நன்மைகள் உள்ளன. அவற்றில் முதன்மையானது: programmer-களுக்கு ஏற்கெனவே பழக்கமான, நிலைபெற்ற மரபுகளுடன் பொருந்துவதால், உங்கள் API-ஐக் கற்பதும் பயன்படுத்துவதும் எளிதாகிறது. அதற்கு அடுத்தபடியாக: உங்கள் API-ஐப் பயன்படுத்தும் program-கள் பழக்கமில்லாத exception-களால் நிரம்பியிருக்காததால், அவற்றைப் படிப்பது எளிது. கடைசியாக (முக்கியத்துவத்திலும் கடைசி), குறைவான exception class-கள் என்றால் குறைவான memory footprint, class-களை load செய்வதில் குறைவான நேரம்.
மிக அதிகமாக மறுபயன்பாடு செய்யப்படும் exception type IllegalArgumentException. பொருத்தமற்ற மதிப்புள்ள argument-ஐ அழைப்பவர் அனுப்பும்போது பொதுவாக வீசப்பட வேண்டிய exception இதுதான்.
எடுத்துக்காட்டாக, ஒரு செயல் எத்தனை முறை திரும்பச் செய்யப்பட வேண்டும் என்பதைக் குறிக்கும் parameter-இல் அழைப்பவர் ஓர் எதிர்மறை எண்ணை அனுப்பினால், வீச வேண்டியது இந்த exception-ஐத்தான். அடிக்கடி
மறுபயன்பாடு செய்யப்படும் இன்னொரு exception IllegalStateException. அழைப்பைப் பெறும் object-இன் state காரணமாக அழைப்பு செல்லாததாக இருக்கும்போது பொதுவாக வீசப்பட வேண்டியது இதுதான்.
எடுத்துக்காட்டாக, ஒரு object முறையாக initialize ஆவதற்கு முன்பே அழைப்பவர் அதைப் பயன்படுத்த முயன்றால், வீச வேண்டியது இந்த exception-ஐத்தான்.
தவறான ஒவ்வொரு method அழைப்பும் இறுதியில் ஒரு செல்லாத argument அல்லது state-க்கே வந்து சேர்கிறது என்று வாதிடலாம்; ஆனால் சில குறிப்பிட்ட வகை செல்லாத argument-கள், state-களுக்கு வேறு exception-கள்
வழக்கமாகப் பயன்படுத்தப்படுகின்றன. null மதிப்புகள் தடைசெய்யப்பட்ட ஒரு parameter-இல் அழைப்பவர் null-ஐ அனுப்பினால், மரபுப்படி IllegalArgumentException-க்குப் பதிலாக NullPointerException வீசப்பட வேண்டும்.
அதுபோலவே, ஒரு sequence-இன் index-ஐக் குறிக்கும் parameter-இல் அழைப்பவர் வரம்புக்கு வெளியே உள்ள மதிப்பை அனுப்பினால், IllegalArgumentException-க்குப் பதிலாக IndexOutOfBoundsException வீசப்பட வேண்டும்.
Exception, RuntimeException, Throwable, Error ஆகியவற்றை நேரடியாக மறுபயன்பாடு செய்யாதீர்கள். இந்த class-களை abstract போலவே கருதுங்கள். இருந்தாலும், ஒரு checked exception-ஐ runtime
exception-ஆக மாற்ற விரும்பினால், நீங்கள் என்ன செய்கிறீர்கள் என்று உண்மையிலேயே தெரிந்திருந்தால், RuntimeException-ஐப் பயன்படுத்தலாம்.
33. Exception-களைப் புறக்கணிக்காதீர்கள்
// Empty catch block ignores exception - Highly suspect!
try {
...
} catch (SomeException e) {
}
காலியான catch block, exception-களின் நோக்கத்தையே தோற்கடிக்கிறது; விதிவிலக்கான சூழல்களைக் கையாள உங்களைக் கட்டாயப்படுத்துவதே அந்த நோக்கம். ஒரு exception-ஐப் புறக்கணிப்பது, தீ எச்சரிக்கை மணியைப் புறக்கணிப்பதற்குச் சமம் – அதோடு, உண்மையிலேயே தீப்பிடித்திருக்கிறதா என்று வேறு யாரும் பார்க்க முடியாதபடி அதை அணைத்துவிடுவதற்கும் சமம்.