Mbinu Zangu Bora – Java
1. Pattern.compile() badala ya String.replaceAll() ndani ya kitanzi
public String replaceAll(String regex, String replacement) {
return Pattern.compile(regex).matcher(this).replaceAll(replacement);
}
Pattern lazima kwanza ikusanywe (compile) kila mara. Ikiwa Pattern ileile inaendeshwa mara nyingi kwenye maandishi mafupi, utendaji ni bora zaidi sana ukitumia tena Pattern
iliyokwisha kukusanywa.
2. StringBuilder badala ya + ndani ya kitanzi
new StringBuilder() ina kasi zaidi kuliko + ndani ya kitanzi.
3. Tumia equals() na hashCode() ipasavyo
Wakati mwingine sehemu zote hujumuishwa katika equals() au hashCode(). Hii inafaa kukagua kama entity imebadilika, lakini si sahihi. Madhumuni ya
equals() si kukagua kama entity imebadilishwa, bali kubaini kama bado ni entity ileile hata kama mfano (instance) wa kitu si uleule.
Kwa maneno mengine: Tumia ufunguo msingi (primary key) pekee!
Hili linahitajika kwa aina zote za mikusanyiko (collections). Ukiita .contains() kwenye List, Java itatumia mbinu ya equals. HashMap pia zinahitaji hashCode().
Mbinu hizi mbili zinapaswa kubatilishwa (override) pamoja.
Mtego wa kawaida ni entity ambazo hazijahifadhiwa na hazina ID. Kuwa mwangalifu hapa na uzihifadhi (persist) mapema ili kuepuka matatizo.
Linganisha:
Mkataba wa mbinu hizi lazima uzingatiwe kila wakati:
4. Mitego ya DateTime mifumo endeshi inapochanganywa
Baadhi ya mifumo endeshi ina saa ya ndani yenye usahihi wa nanosekunde, mingine milisekunde. Kwa hiyo ZonedDateTime.now() haifanyi kazi kwa namna ileile kwenye majukwaa yote mawili na inaharibu
equals(). Badala yake, KILA MARA rekebisha usahihi!
/**
* 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);
}
Annotation zifuatazo za JPA pia zinategemea ZonedDateTime.now() na hazipaswi kutumiwa kama zilivyo:
@CreationTimestamp@UpdateTimestamp@PastOrPresent
5. Usitangaze aina changamano za data ndani ya mstari
Kila mara unda madarasa yako mwenyewe kwa aina changamano za data badala ya kuweka Pair, List n.k. ndani ya nyingine.
Mbaya:
// unlesbar
public Triple<List<Pair<List<List<ChatMessage>>, ZonedDateTime>>, Language, ChatRoom> getChatMessagesGroupedByDay();
Bora zaidi:
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 ...
}
Faida:
- Majina bora kuliko
getFirst()nagetSecond() - Matumizi yanafafanuliwa wazi na getter na setter (Je, darasa hili ni immutable? Orodha ipi inaweza kuongezewa? Kitu hiki kinapaswa kuundwa vipi?) → Ukapsuli unaeleza matumizi ya msimbo wenyewe
- Usomekaji bora zaidi kwa kiasi kikubwa
Isipokuwa:
Kwa mahusiano rahisi, Pair ni nzuri sana! Kwa mfano:
Pair<User, Training> pair = this.addCurrentUserToTraining(training);
6. Caching – kwa njia sahihi (mwongozo wa usanidi wa chini kabisa)
Cache (hutamkwa /kæʃ/, kama "cash") kimsingi ni Map tu inayohifadhi maingizo. Mambo changamano zaidi kama kubatilisha kwa kuzingatia muda na mengineyo yameachwa kwa makusudi.
Si lazima kila mara kutumia maktaba ikiwa Map rahisi inatosha! Wasanidi wengi hawalielewi hili na, kwa hofu, huchagua maktaba kila mara. Jambo muhimu zaidi katika caching ni kubatilisha (invalidate) cache kwa wakati unaofaa.
Mtego #1: Uvujaji wa kumbukumbu
Uvujaji wa kumbukumbu (memory leak) unaweza kutokea kwa urahisi sana katika caching. Ukisahau tu kubatilisha (override) equals() na hashCode() ipasavyo, tayari una HashMap
inayovujisha kumbukumbu. Njia iliyo salama daima ni kuunda funguo za Map kwa mkono. Ingefaa kutumia ID ya entity pekee; vinginevyo Map inavuja pindi tu sehemu moja
inapobadilika. Na hilo ni sahihi, kwa sababu hata jina la kwanza la mtumiaji likibadilika, bado ni mtumiaji yuleyule. Linganisha: 3. Tumia equals() na hashCode()
ipasavyo
Mtego #2: Usalama wa thread
Ni bora cache itumie ConcurrentHashMap, inayotoa operesheni za atomiki zilizo salama kwa thread. Map inabadilishwa kupitia mwito mmoja tu – computeIfAbsent() – na kwa njia hiyo usalama wa thread (thread safety) unapatikana:
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());
}
Kama msimbo ungekuwa mrefu zaidi na kungekuwa na miito kadhaa, mbinu nzima ingehitaji kuwa synchronized.
7. Serialization – bado inahitajika?
Swali langu: Kuna kanuni ya Sonar inayoitwa Fields in a “Serializable” class should either be transient or serializable (sehemu katika darasa “Serializable” zinapaswa kuwa transient au serializable). Hujitokeza unapoongeza sehemu isiyoweza kufanyiwa serialization kwenye darasa lako. Kanuni hiyo inasema:
Kwa mfano, mzigo ukiwa mkubwa, mifumo mingi ya programu za J2EE huhifadhi vitu kwenye diski, na kitu kinachodaiwa kuwa Serializable lakini chenye wanachama wa data wasio transient na wasioweza kufanyiwa serialization kinaweza kusababisha programu kuanguka na kufungua mlango kwa washambulizi.
Kwangu hili linasikika kama limepitwa na wakati. Katika Effective Java 3rd Edition inaelezwa kwamba serialization haina umuhimu unapotengeneza programu mpya. Pia, kuondoa serialization kutoka Java ni “lengo la muda mrefu” la Oracle. Mbunifu mkuu wa Java mwenyewe, Mark Reinhold, alisema:
“Serialization ilikuwa kosa baya mno mwaka 1997.”
Swali la kama mfumo wa Spring unaweza kuhifadhi vitu kwenye diski lilikwisha kuulizwa hapa:
Kwa hiyo ninauliza:
Je, seva zozote za kisasa za programu, mifumo (frameworks) au utekelezaji wa J2EE wenyewe (hasa vipengele vya Java vinavyoshirikiana na CDI, EJB, JPA) huhifadhi vitu kwenye diski mzigo unapokuwa mkubwa sana?
Au kauli hii imepitwa na wakati?
Jibu:
Kwa ufupi: Ikiwa hutengenezi programu ya wavuti iliyosambazwa yenye hali (stateful), hujawasha uhifadhi wa kudumu wa vikao (session persistence) na unatumia cache za kwenye kumbukumbu pekee, huenda usihitaji Serializable mahali popote.
Maelezo:
Kwa unakilishaji wa vikao (session replication), serialization ya Java bado ni lazima katika vipimo vya hivi karibuni vya Servlet: https://jakarta.ee/specifications/servlet/5.0/jakarta-servlet-spec-5.0.html#distributed-environments
Matumizi ya kawaida: una seva kadhaa nyuma ya kisawazishi cha mzigo (load balancer), na unapoingia kwenye seva moja, unapata kuki ya JSESSIONID. Ikiwa kisawazishi cha mzigo kitakupeleka kwenye seva
nyingine kwa ombi linalofuata, sifa zako za kuingia na sifa nyingine za kikao zitafanyiwa serialization na kunakiliwa kwenye seva mpya, ili JSESSIONID yako iendelee kuwa halali huko.
Matumizi mengine yanayohitaji serialization kwa sifa za kikao ni pale seva ya programu inapoamua kuhamishia vikao kwenye diski. Kwa mfano katika Tomcat 8, hili linaweza kusanidiwa litokee pale kuna vikao vingi mno kuweza kuhifadhiwa kwenye kumbukumbu, au seva inapowashwa upya na unataka vikao viendelee kuwa hai. Tazama https://tomcat.apache.org/tomcat-9.0-doc/config/manager.html
Kila seva ya programu inayooana na Servlet 5.0 lazima iauni vitu vya Serializable ndani ya vikao vya HTTP kwa chaguo-msingi kwa matumizi ya aina hii.
Kwa baadhi ya seva, hili linaweza kurekebishwa au kubadilishwa, kwa mfano kwa kutumia serialization ya Jackson-JSON au kitu kinachofanana. Tazama kwa mfano https://stackoverflow.com/questions/46459707/how-to-use-jackson-instead-of-jdkserializationredisserializer-in-spring
Matumizi mengine ya kawaida ni caching. Hutumika kufanya vitu vipatikane haraka iwezekanavyo, ikiwezekana kwenye kumbukumbu, lakini huenda vikahamishiwa kwa muda kwenye diski au kwenye seva ya nje ya cache.
Kuna API sanifu kwa ajili ya hili (JSR-107) ambayo kwa makusudi haikulazimisha vitu kuwa Serializable, lakini utekelezaji mwingi bado hutumia serialization ya Java kwa chaguo-msingi kwa sehemu ya kuhamisha.
Tena, unaweza kurekebisha hizi ili ziauni mbinu nyingine za serialization, kwa mfano kwa ehcache: https://www.ehcache.org/documentation/3.8/serializers-copiers.html
8. Hibernate: mkakati wa fetching
Kwa kuzingatia makala EAGER fetching is a code smell, iliyoandikwa na mjuaji mmoja mwenye haiba kubwa ambaye pia ni mchangiaji wa
Hibernate, LAZY inapendekezwa kama FetchType. Hata kama baadhi ya aina za data hazina maana bila ufunguo wake wa kigeni (foreign key), aina hizi bado hutafutwa peke yake
(getCount(), majedwali ya data n.k.).
Hapa kuna hali zote zikiwa zimepangwa kulingana na kiwango cha kuridhisha kwa suluhisho, kuanzia lisiloridhisha zaidi:
1. Hali mbaya zaidi: LazyInitializationException
Hakuna haja ya maneno. Sote tunashiriki maumivu haya.
2. Suluhisho la haraka
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. Unproxy moja kwa moja ndani ya service
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. Fetch moja kwa moja ndani ya service (fanya hivi!)
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 imeunganishwa kikamilifu na Hibernate JPA. Inatambua entity zinapobadilika na ni rahisi kusanidi:
@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;
// ...
}
Katika mazingira ya ndani (local), utafutaji umezimwa:
// 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();
}
Utafutaji wa kukadiria & tabia ya utafutaji
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
Kutatua hitilafu za Lucene kwa Luke
Toleo letu la Hibernate Search lazima litatuliwe hitilafu kwa kutumia Luke toleo 6.6.0. Inafanya kazi na toleo hili tu!
- Pakua: https://github.com/DmitryKey/luke/releases
- Kwa Luke unaweza kufungua faharasa (indexes) kutoka kwenye folda
target/. - Njia nzuri ya kuona kama entity zinaingizwa kwenye faharasa ipasavyo hata kidogo.
- Utafutaji pia unaweza kuanzishwa moja kwa moja ndani ya Luke.
ElasticSearch
Hibernate Search pia inaauni ElasticSearch (ambayo hutumika badala ya Lucene na ina utafutaji wa kukadiria ulio bora zaidi – unaofanana zaidi na Google).