என் Best Practices – Java
1. Loop-இல் String.replaceAll()-க்குப் பதிலாக Pattern.compile()
public String replaceAll(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}
Pattern எப்போதும் முதலில் compile செய்யப்பட வேண்டும். ஒரே Pattern சிறிய String-கள் மீது அடிக்கடி இயங்கினால், compile செய்த Pattern-ஐ
மீண்டும் பயன்படுத்துவது performance-ஐக் கணிசமாக மேம்படுத்தும்.
2. Loop-இல் +-க்குப் பதிலாக StringBuilder
Loop-இல் +-ஐ விட new StringBuilder() அதிக performance தரும்.
3. equals(), hashCode()-ஐச் சரியாகப் பயன்படுத்துதல்
சில நேரங்களில் equals() அல்லது hashCode()-இல் எல்லா field-களும் சேர்க்கப்படுகின்றன. ஒரு entity மாற்றப்பட்டதா என்று சரிபார்க்க இது பயனுள்ளதுபோல் தோன்றும், ஆனால் இது தவறு.
equals()-இன் நோக்கம் ஒரு entity மாற்றப்பட்டதா என்று சரிபார்ப்பது அல்ல; object instance வேறாக இருந்தாலும் அது அதே entity-தானா என்று கண்டறிவதுதான்.
அதாவது: Primary key-ஐ மட்டும் பயன்படுத்துங்கள்!
எல்லா வகை Collection-களுக்கும் இது தேவை. ஒரு List-இல் .contains()-ஐ அழைத்தால், Java equals method-ஐப் பயன்படுத்தும். HashMap-களுக்கு hashCode()-உம் தேவை.
இரண்டையும் சேர்த்தே override செய்ய வேண்டும்.
ID இல்லாத, இன்னும் save செய்யப்படாத entity-கள் ஒரு பொதுவான பொறி. இங்கே கவனமாக இருங்கள்; சிக்கல்களைத் தவிர்க்க முன்கூட்டியே persist செய்யுங்கள்.
ஒப்பிட்டுப் பாருங்கள்:
இந்த method-களின் contract-ஐ எப்போதும் பின்பற்ற வேண்டும்:
4. வெவ்வேறு operating system-களைக் கலந்து பயன்படுத்தும்போது DateTime பொறிகள்
சில operating system-களின் உள் கடிகாரம் nanosecond அளவிலும், சிலவற்றினுடையது millisecond அளவிலும் இயங்குகிறது. இதனால் ZonedDateTime.now() இரண்டு platform-களிலும் ஒரே மாதிரி செயல்படாது;
equals()-ஐயும் உடைத்துவிடும். எனவே எப்போதும் precision-ஐச் சரிசெய்யுங்கள்!
/**
* truncating the precision, because it relies on the internal clock of linux/macOs/windows.
* windows and older macOS versions use only milliseconds, whereas linux and new macOS versions use microsecond precision.
*/
public static ZonedDateTime getNow() {
return ZonedDateTime.now().truncatedTo(DateTimeUtil.PRECISION);
}
கீழ்க்காணும் JPA method-களும் ZonedDateTime.now()-ஐ நம்பியுள்ளன; அவற்றை ஒருபோதும் அப்படியே (plain) பயன்படுத்தக் கூடாது:
@CreationTimestamp@UpdateTimestamp@PastOrPresent
5. சிக்கலான data type-களை inline-ஆக அறிவிக்காதீர்கள்
Pair, List போன்றவற்றை ஒன்றுக்குள் ஒன்றாக அடுக்குவதற்குப் பதிலாக, சிக்கலான data type-களுக்கு எப்போதும் தனி class-களை உருவாக்குங்கள்.
மோசம்:
// unlesbar
public Triple<List<Pair<List<List<ChatMessage>>, ZonedDateTime>>, Language, ChatRoom> getChatMessagesGroupedByDay();
இன்னும் சிறப்பாக:
public class FTOChatMessageCollection {
private List<FTOChatMessageCollectionPerDay> messagesPerDay;
private Language language;
private ChatRoom chatRoom;
// constructor, getter & setter ...
}
public class FTOChatMessageCollectionPerDay {
private List<FTOChatMessage> messages;
private ZonedDateTime day;
// constructor, getter & setter ...
}
நன்மைகள்:
getFirst(),getSecond()-ஐ விடச் சிறந்த பெயர்கள்- Getter, setter மூலம் பயன்பாடு தெளிவாக வரையறுக்கப்படுகிறது (class immutable-ஆ? எந்த list-இல் புதிய உறுப்புகளைச் சேர்க்கலாம்? Object-ஐ எப்படி உருவாக்க வேண்டும்?) → Encapsulation code-ஐ எப்படிப் பயன்படுத்த வேண்டும் என்பதைத் தானாகவே ஆவணப்படுத்துகிறது
- மிகச் சிறந்த வாசிப்புத்திறன்
விதிவிலக்குகள்:
எளிய உறவுகளுக்கு Pair அருமை! எடுத்துக்காட்டாக:
Pair<User, Training> pair = this.addCurrentUserToTraining(training);
6. Caching – ஆனால் சரியான முறையில் (குறைந்தபட்ச setup வழிகாட்டி)
Cache (உச்சரிப்பு /kæʃ/, ஆங்கில “cash” போல) அடிப்படையில் entry-களை வைத்திருக்கும் ஒரு Map மட்டுமே. நேர அடிப்படையிலான invalidation போன்ற சிக்கலான விஷயங்கள் இங்கே வேண்டுமென்றே தவிர்க்கப்பட்டுள்ளன.
ஒரு எளிய Map போதுமானதாக இருக்கும் இடத்தில் எப்போதும் library தேவையில்லை! பல developer-கள் இதைப் புரிந்துகொள்வதில்லை; பயத்தின் காரணமாக கொள்கையளவிலேயே library-களைப் பயன்படுத்துகிறார்கள். Caching-இல் மிக முக்கியமான அம்சம், சரியான நேரத்தில் invalidate செய்வதுதான்.
பொறி #1: Memory Leak
Cache செய்யும்போது Memory Leak மிக எளிதாக நேரலாம். equals(), hashCode()-ஐ ஒழுங்காக override செய்ய மறந்தாலே போதும் – உங்கள் HashMap memory-ஐ
leak செய்யத் தொடங்கிவிடும். எப்போதும் பாதுகாப்பான ஒரு வழி: Map key-களைக் கைமுறையாக உருவாக்குவது. ஒரு entity-இன் ID-ஐ மட்டுமே சேர்ப்பது சிறந்தது; இல்லையென்றால் ஒரு field மாறினாலே
Map leak ஆகிவிடும். அதுவும் சரியானதே, ஏனெனில் ஒரு User-இன் முதல் பெயர் மாறினாலும் அவர் அதே User-தான். ஒப்பிடுக: 3. equals(), hashCode()-ஐச்
சரியாகப் பயன்படுத்துதல்
பொறி #2: Thread Safety
Cache-க்குச் சிறந்தது ஒரு ConcurrentHashMap; அது atomic, thread-safe operation-களை வழங்குகிறது. Map ஒரே ஒரு call மூலம் மட்டுமே மாற்றப்படுகிறது – computeIfAbsent() – இதனால் Thread Safety உறுதியாகிறது:
private ConcurrentMap<String, T> cache = new ConcurrentHashMap<>();
protected T getOrCompute(final String key, final Supplier<T> computeFunction) {
// thread safe, because `computeIfAbsent` is atomic, so no need for `synchronized` here.
// if you extend this to more than 1 map operation, you should synchronize it
return this.getCacheMap().computeIfAbsent(key, k -> computeFunction.get());
}
Code இன்னும் நீளமாகவும் பல call-கள் கொண்டதாகவும் இருந்தால், method முழுவதையும் synchronized ஆக்க வேண்டியிருக்கும்.
7. Serialization – இன்னும் தேவையா?
என் கேள்வி: ஒரு Sonar விதி உள்ளது, அதன் பெயர் Fields in a “Serializable” class should either be transient or serializable. உங்கள் class-இல் serializable அல்லாத ஒரு field-ஐச் சேர்க்கும்போது இது தோன்றும். அந்த விதி இப்படிச் சொல்கிறது:
எடுத்துக்காட்டாக, அதிக load இருக்கும்போது பெரும்பாலான J2EE application framework-கள் object-களை disk-க்கு flush செய்கின்றன; transient அல்லாத, serializable அல்லாத data member-களைக் கொண்ட, Serializable எனப்படும் ஒரு object program crash-களை உண்டாக்கலாம், தாக்குதல் நடத்துபவர்களுக்குக் கதவைத் திறந்துவிடலாம்.
இது எனக்குக் காலாவதியான கருத்தாகத் தோன்றுகிறது. புதிய மென்பொருளை உருவாக்கும்போது Serialization பொருத்தமற்றது என்று Effective Java 3rd Edition கூறுகிறது. மேலும், Java-விலிருந்து Serialization-ஐ நீக்குவது Oracle-இன் ‘நீண்டகால இலக்கு’. Java-வின் தலைமைக் கட்டமைப்பாளர் Mark Reinhold-ஏ இப்படிக் கூறியுள்ளார்:
“1997-இல் Serialization ஒரு பயங்கரமான தவறு.”
Spring framework object-களை flush செய்யுமா என்ற கேள்வி இங்கே ஏற்கெனவே கேட்கப்பட்டுள்ளது:
எனவே என் கேள்வி:
நவீன application server-களோ, framework-களோ, அல்லது J2EE implementation-ஏ (குறிப்பாக CDI, EJB, JPA-வுடன் இணைந்து செயல்படும் Java component-கள்) அதிக load இருக்கும்போது object-களை disk-க்கு flush செய்கின்றனவா?
அல்லது இந்தக் கூற்று காலாவதியாகிவிட்டதா?
பதில்:
TL;DR நீங்கள் stateful distributable web application-ஐ உருவாக்கவில்லை, session persistence-ஐ இயக்கவில்லை, in-memory cache-களை மட்டுமே பயன்படுத்துகிறீர்கள் என்றால், எங்குமே Serializable இல்லாமலேயே சமாளித்துவிடலாம்.
விளக்கம்:
Session replication-க்கு, சமீபத்திய Servlet Specification-இலும் Java Serialization இன்னும் கட்டாயம்: https://jakarta.ee/specifications/servlet/5.0/jakarta-servlet-spec-5.0.html#distributed-environments
ஒரு பொதுவான பயன்பாடு: ஒரு load balancer-க்குப் பின்னால் பல server-கள் இருக்கின்றன; நீங்கள் ஒரு server-இல் login செய்தால் ஒரு JSESSIONID cookie கிடைக்கும். அடுத்த request-இல்
load balancer உங்களை வேறொரு server-க்கு அனுப்பினால், உங்கள் login attribute-களும் பிற session-scoped attribute-களும் serialize செய்யப்பட்டு புதிய server-இல் replicate செய்யப்படும்; இதனால் அங்கும் உங்கள் JSESSIONID செல்லுபடியாகும்.
உங்கள் session-scoped attribute-களுக்கு Serialization தேவைப்படும் இன்னொரு சூழல்: application server, session-களை disk-க்கு swap செய்ய முடிவெடுக்கும்போது. எடுத்துக்காட்டாக Tomcat 8-இல், memory-இல் வைத்திருக்க முடியாத அளவுக்கு session-கள் அதிகமாகும்போதோ, server restart ஆன பிறகும் session-களை உயிர்ப்புடன் வைத்திருக்க விரும்பும்போதோ இது இயங்கும்படி அமைக்கலாம். பார்க்க: https://tomcat.apache.org/tomcat-9.0-doc/config/manager.html
இத்தகைய பயன்பாடுகளுக்காக, Servlet-5.0 உடன் இணக்கமான ஒவ்வொரு application server-உம் இயல்பாகவே Http Session-களில் Serializable object-களை ஆதரிக்க வேண்டும்.
சில server-களில் இதை மாற்றியமைக்கலாம் அல்லது override செய்யலாம் – எடுத்துக்காட்டாக Jackson-JSON serialization அல்லது அதுபோன்ற ஒன்றைப் பயன்படுத்தி. பார்க்க: https://stackoverflow.com/questions/46459707/how-to-use-jackson-instead-of-jdkserializationredisserializer-in-spring
இன்னொரு பொதுவான பயன்பாடு caching. Object-களை முடிந்தவரை வேகமாக அணுகக்கூடியதாக வைத்திருக்க இது பயன்படுகிறது – இயன்றால் memory-இல், ஆனால் தேவைப்பட்டால் தற்காலிகமாக disk-க்கோ ஒரு வெளி cache server-க்கோ மாற்றி.
இதற்கு ஒரு standard API (JSR-107) உள்ளது; அது வேண்டுமென்றே Serializable object-களைக் கட்டாயமாக்கவில்லை, ஆனாலும் பெரும்பாலான implementation-கள் offloading பகுதிக்கு இயல்பாக Java Serialization-ஐயே பயன்படுத்துகின்றன.
இவற்றையும் வேறு serialization முறைகளை ஆதரிக்கும்படி மாற்றியமைக்கலாம், எடுத்துக்காட்டாக ehcache-க்கு: https://www.ehcache.org/documentation/3.8/serializers-copiers.html
8. Hibernate: Fetching உத்தி
EAGER fetching is a code smell என்ற கட்டுரையைக் கருத்தில் கொண்டால் – அதை எழுதியவர் அபாரமான கவர்ச்சி கொண்ட, எல்லாம் தெரிந்தவர்போல் பேசும் ஒருவர்; Hibernate
contributor-உம் கூட – FetchType-ஆக LAZY-ஐத் தேர்வுசெய்வதே நல்லது. சில data type-கள் foreign key இல்லாமல் அர்த்தமற்றவையாக இருந்தாலும், அந்த type-கள் தனியாகவும்
query செய்யப்படுகின்றன (getCount(), Data-Table-கள் போன்றவை).
தீர்வு எந்த அளவுக்குத் திருப்திகரமானது என்ற வரிசையில் எல்லாச் சூழல்களும் கீழே உள்ளன – மிகக் குறைவாகத் திருப்தி தருவதிலிருந்து தொடங்கி:
1. மிக மோசமான நிலை: LazyInitializationException
வேறு எதுவும் சொல்லத் தேவையில்லை. இந்த வலி நம் எல்லோருக்கும் தெரிந்ததே.
2. அவசரத் தீர்வு
public void updateChatRoom(ChatMessage message) {
// reload here or LazyInitializationException would be thrown
message = this.chatMessageService.reload(message); // not optimal: second roundtrip to the database - could easily take 100ms!
// we missed the chance to unproxy without a performance hit.
ChatRoom chatRoom = message.getChatRoom().getUpdatedAt(DateTimeUtil.getNow());
}
3. Service-இலேயே நேரடியாக unproxy செய்தல்
public ChatMessage getChatMessage(final Long id, boolean withRoom) {
// assume this is the same service + the same transaction.
// so we are in the same persistence context + everything will be performed together at the transaction flush.
// perfect: no additional roundtrip like in example 2.!
ChatMessage message = this.chatMessageService.getById(id);
if (withRoom) {
ChatRoom chatRoom = this.chatMessageService.unproxy(message.getChatRoom());
}
// we should not call message.setChatRoom() -> this getter should not even exist as Hibernate would
// map this relation for us! setting it again would perform an update on our foreign key!!
// so we have no option to set the ChatRoom back to the message and have to pass it everywhere in a Tuple or something similar
return ???;
}
4. Service-இலேயே நேரடியாக fetch செய்தல் (இதையே செய்யுங்கள்!)
public ChatMessage getChatMessage(final Long id, boolean withRoom) {
// all aforementioned problems solved
return this.executeSingleResultQuery((query, cb, root) -> {
if (withRoom) {
root.fetch(ChatRoomEntity_.partition).fetch(ChatPartitionEntity_.chatModule);
}
return cb.equal(root.get(ChatRoomEntity_.id), id);
});
}
9. Hibernate Search
Hibernate Search, Hibernate JPA-வுடன் தடையின்றி இணைந்துள்ளது. Entity-கள் மாறும்போது அது அறிந்துகொள்கிறது; எளிதாக configure செய்யவும் முடியும்:
@Entity
@Indexed(interceptor = SearchableChatMessageInterceptor.class) // hier können Einträge von der Indexierung ausgeschlossen werden
@FullTextFilterDef(name = "SearchableChatMessageFilter", impl = SearchableChatMessageFilter.class) // hier wird der Key festgelegt
public class ChatMessageEntity implements Searchable { // das Searchable dient als Marker-Interface
@Column
@Field // dieses Feld wird indexiert
private String message;
@ManyToOne(targetEntity = ChatRoomEntity.class, fetch = FetchType.LAZY, optional = false)
@IndexedEmbedded(includeEmbeddedObjectId = true, targetElement = ChatRoomEntity.class) // indexiert den Fremschlüssel mit.
// Über dieses Entity wird auch die Message gesucht,
// wie in @FullTextFilterDef -> SearchableChatMessageFilter definiert
private ChatRoom chatRoom;
// ...
}
Local சூழலில் search அணைக்கப்பட்டுள்ளது:
// DefaultInstanceInitializer.java
// we don't need the global search everywhere, givin an option to override this setting here
protected boolean isBuildSearchIndexes() {
return !Constants.Mode.isDebug();
}
Fuzzy search & தேடல் நடத்தை
if ((needle.split(" ").length > 1 || needle.contains("\"")) && !searchExact) {
/*
* Simple Query String. Provides basic functionality like:
*
* boolean (AND using “+”, OR using “|”, NOT using “-“)
* prefix (prefix*)
* phrase (“some phrase”)
* precedence (using parentheses)
* fuzzy (fuzy~2)
* near operator for phrase queries (“some phrase”~3)
*/
query = qb.simpleQueryString()
.onField(field)
.andFields(moreFields)
.withAndAsDefaultOperator()
.matching(needle)
.createQuery();
} // ...
Lucene
Luke மூலம் Lucene-ஐ debug செய்தல்
எங்கள் Hibernate Search பதிப்பை Luke 6.6.0 பதிப்பைக் கொண்டுதான் debug செய்ய வேண்டும். இந்தப் பதிப்பில் மட்டுமே அது வேலை செய்கிறது!
- பதிவிறக்கம்: https://github.com/DmitryKey/luke/releases
- இதைக் கொண்டு
target/folder-இல் உள்ள index-களைத் திறக்கலாம். - Entity-கள் உண்மையில் சரியாக index செய்யப்படுகின்றனவா என்று பார்க்க இது நல்ல இடம்.
- Luke-இலிருந்தே ஒரு தேடலையும் இயக்கலாம்.
ElasticSearch
Hibernate Search, ElasticSearch-ஐயும் ஆதரிக்கிறது (அப்போது அது Lucene-க்குப் பதிலாகப் பயன்படுத்தப்படும்; அதன் fuzzy search இன்னும் சிறப்பானது / Google போன்ற அனுபவம் அதிகம்).