JavaRush /Java Blog /Random-TL /Coffee break #85. Tatlong mga aralin sa Java ang natutuna...

Coffee break #85. Tatlong mga aralin sa Java ang natutunan ko sa mahirap na paraan. Paano gamitin ang mga SOLID na prinsipyo sa code

Nai-publish sa grupo

Tatlong Mga Aralin sa Java na Natutunan Ko sa Mahirap na Paraan

Source: Medium Learning Java ay mahirap. Natuto ako sa mga pagkakamali ko. Ngayon ay maaari ka ring matuto mula sa aking mga pagkakamali at mapait na karanasan, na hindi mo naman kailangang magkaroon. Coffee break #85.  Tatlong mga aralin sa Java ang natutunan ko sa mahirap na paraan.  Paano gamitin ang mga SOLID na prinsipyo sa code - 1

1. Maaaring magdulot ng problema ang Lambdas.

Ang mga Lambdas ay kadalasang lumalampas sa 4 na linya ng code at mas malaki kaysa sa inaasahan. Ito ay nagpapabigat sa memorya sa pagtatrabaho. Kailangan mo bang baguhin ang isang variable mula sa lambda? Hindi mo magagawa iyon. Bakit? Kung maa-access ng lambda ang mga variable ng site ng tawag, maaaring magkaroon ng mga isyu sa threading. Samakatuwid hindi mo maaaring baguhin ang mga variable mula sa lambda. Ngunit gumagana nang maayos ang Happy path sa lambda. Pagkatapos ng isang runtime failure, matatanggap mo ang tugon na ito:

at [CLASS].lambda$null$2([CLASS].java:85)
at [CLASS]$$Lambda$64/730559617.accept(Unknown Source)
Mahirap sundan ang lambda stack trace. Ang mga pangalan ay nakakalito at mahirap subaybayan at i-debug. Mas maraming lambdas = mas maraming stack trace. Ano ang pinakamahusay na paraan upang i-debug ang mga lambdas? Gumamit ng mga intermediate na resulta.

map(elem -> {
 int result = elem.getResult();
 return result;
});
Ang isa pang magandang paraan ay ang paggamit ng mga advanced na diskarte sa pag-debug ng IntelliJ. Gamitin ang TAB upang piliin ang code na gusto mong i-debug at pagsamahin ito sa mga intermediate na resulta. “Kapag huminto kami sa isang linyang naglalaman ng lambda, kung pinindot namin ang F7 (step into), iha-highlight ng IntelliJ ang fragment na kailangang i-debug. Maaari naming ilipat ang block sa pag-debug gamit ang Tab, at kapag napagpasyahan namin iyon, pindutin muli ang F7." Paano i-access ang mga variable ng site ng tawag mula sa lambda? Maaari mo lamang i-access ang pangwakas o aktwal na mga huling variable. Kailangan mong gumawa ng wrapper sa paligid ng mga variable ng lokasyon ng tawag. Alinman sa paggamit ng AtomicType o gamit ang iyong sariling uri. Maaari mong baguhin ang ginawang variable wrapper gamit ang lambda. Paano malutas ang mga problema sa stack trace? Gumamit ng mga pinangalanang function. Sa ganitong paraan, mabilis kang makakahanap ng responsableng code, masusuri ang lohika, at malulutas ang mga problema. Gumamit ng mga pinangalanang function upang alisin ang misteryosong stack trace. Naulit ba ang parehong lambda? Ilagay ito sa isang pinangalanang function. Magkakaroon ka ng isang punto ng sanggunian. Ang bawat lambda ay nakakakuha ng nabuong function, na nagpapahirap sa pagsubaybay.

lambda$yourNamedFunction
lambda$0
Ang mga pinangalanang function ay lumulutas ng ibang problema. Malaking lambdas. Pinaghihiwa-hiwalay ng mga pinangalanang function ang malalaking lambda, gumawa ng mas maliliit na piraso ng code, at gumawa ng mga pluggable na function.

.map(this::namedFunc1).filter(this::namedFilter1).map(this::namedFunc2)

2. Mga problema sa mga listahan

Kailangan mong magtrabaho kasama ang mga listahan ( Mga Listahan ). Kailangan mo ng HashMap para sa data. Para sa mga tungkulin kakailanganin mo ng TreeMap . Ang listahan ay nagpapatuloy. At walang paraan na maiiwasan mo ang pagtatrabaho sa mga koleksyon. Paano gumawa ng listahan? Anong uri ng listahan ang kailangan mo? Dapat ba itong hindi nababago o nababago? Ang lahat ng mga sagot na ito ay nakakaapekto sa hinaharap ng iyong code. Piliin ang tamang listahan nang maaga para hindi ka magsisi sa bandang huli. Ang Arrays::asList ay lumilikha ng "end-to-end" na listahan. Ano ang hindi mo magagawa sa listahang ito? Hindi mo maaaring baguhin ang laki nito. Siya ay hindi nababago. Ano ang maaari mong gawin dito? Tukuyin ang mga elemento, pag-uuri, o iba pang mga operasyon na hindi nakakaapekto sa laki. Gumamit ng Arrays::asList nang mabuti dahil ang laki nito ay hindi nababago ngunit ang mga nilalaman nito ay hindi. bagong ArrayList() ay lumilikha ng bagong listahan na "nababago". Anong mga operasyon ang sinusuportahan ng nilikhang listahan? Iyon lang, at ito ay isang dahilan upang mag-ingat. Lumikha ng mga nababagong listahan mula sa mga hindi nababago gamit ang bagong ArrayList() . Ang List::of ay lumilikha ng isang "hindi mababago" na koleksyon. Ang laki at nilalaman nito ay hindi nagbabago sa ilalim ng ilang mga kundisyon. Kung ang nilalaman ay primitive na data, tulad ng int , ang listahan ay hindi nababago. Tingnan ang sumusunod na halimbawa.

@Test
public void testListOfBuilders() {
  System.out.println("### TESTING listOF with mutable content ###");

  StringBuilder one = new StringBuilder();
  one.append("a");

  StringBuilder two = new StringBuilder();
  two.append("a");

  List<StringBuilder> asList = List.of(one, two);

  asList.get(0).append("123");

  System.out.println(asList.get(0).toString());
}
### Listahan ng PAGSUBOK NG may nababagong nilalaman ### a123
Kailangan mong lumikha ng mga hindi nababagong bagay at ipasok ang mga ito sa List::of . Gayunpaman, ang List::of ay hindi nagbibigay ng anumang garantiya ng immutability. Ang List::of ay nagbibigay ng immutability, reliability, at readability. Alamin kung kailan gagamit ng nababago at kailan gagamit ng mga hindi nababagong istruktura. Ang listahan ng mga argumento na hindi dapat baguhin ay dapat na nasa hindi nababagong listahan. Ang isang nababagong listahan ay maaaring isang nababagong listahan. Unawain kung anong koleksyon ang kailangan mo para makagawa ng maaasahang code.

3. Ang mga anotasyon ay nagpapabagal sa iyo

Gumagamit ka ba ng mga anotasyon? Naiintindihan mo ba sila? Alam mo ba kung ano ang ginagawa nila? Kung sa tingin mo ay angkop ang Naka-log na anotasyon para sa bawat pamamaraan, mali ka. Ginamit ko ang Log to log method arguments. Nagulat ako, hindi ito gumana.

@Transaction
@Method("GET")
@PathElement("time")
@PathElement("date")
@Autowired
@Secure("ROLE_ADMIN")
public void manage(@Qualifier('time')int time) {
...
}
Ano ang mali sa code na ito? Maraming configuration digest dito. Makakaharap mo ito ng maraming beses. Ang pagsasaayos ay halo-halong may regular na code. Hindi masama sa kanyang sarili, ngunit nakakakuha ito ng iyong mata. Kailangan ang mga anotasyon upang mabawasan ang boilerplate code. Hindi mo kailangang magsulat ng logic sa pag-log para sa bawat endpoint. Hindi na kailangang mag-set up ng mga transaksyon, gamitin ang @Transactional . Binabawasan ng mga anotasyon ang pattern sa pamamagitan ng pagkuha ng code. Walang malinaw na panalo dito dahil pareho silang nasa laro. Gumagamit pa rin ako ng XML at mga anotasyon. Kapag nakakita ka ng umuulit na pattern, pinakamahusay na ilipat ang logic sa anotasyon. Halimbawa, ang pag-log ay isang magandang opsyon sa anotasyon. Moral: huwag gumamit nang labis ng mga anotasyon at huwag kalimutan ang XML.

Bonus: Maaaring may mga problema ka sa Opsyonal

Gagamitin mo ang orElse mula sa Opsyonal . Ang hindi kanais-nais na pag-uugali ay nangyayari kapag hindi mo naipasa ang orElse constant . Dapat mong malaman ito upang maiwasan ang mga problema sa hinaharap. Tingnan natin ang ilang halimbawa. Kapag ang getValue(x) ay nagbalik ng isang halaga, ang getValue(y) ay isasagawa . Ang pamamaraan sa orElse ay isinasagawa kung ang getValue(x) ay nagbabalik ng isang hindi walang laman na Opsyonal na halaga .

getValue(x).orElse(getValue(y)
                  .orElseThrow(() -> new NotFoundException("value not present")));

public Optional<Value> getValue(Source s)
{
  System.out.println("Source: " + s.getName());
  
  // returns value from s source
}

// when getValue(x) is present system will output
Source: x
Source: y
Gumamit ng orElseGet . Hindi ito magpapatupad ng code para sa mga hindi walang laman na Opsyonal .

getValue(x).orElseGet(() -> getValue(y)
                  .orElseThrow(() -> new NotFoundException("value not present")));

public Optional<Value> getValue(Source s)
{
  System.out.println("Source: " + s.getName());
  
  // returns value from s source
}

// when getValue(x) is present system will output
Source: x

Konklusyon

Ang pag-aaral ng Java ay mahirap. Hindi ka maaaring matuto ng Java sa loob ng 24 na oras. Hasain ang iyong mga kasanayan. Maglaan ng oras, matuto at maging mahusay sa iyong trabaho.

Paano gamitin ang mga SOLID na prinsipyo sa code

Source: Cleanthecode Ang pagsulat ng maaasahang code ay nangangailangan ng mga SOLID na prinsipyo. Sa ilang mga punto kailangan nating lahat na matuto kung paano magprogram. At maging tapat tayo. Kami ay BOBO. At ang aming code ay pareho. Thank God meron tayong SOLID. Coffee break #85.  Tatlong mga aralin sa Java ang natutunan ko sa mahirap na paraan.  Paano Gamitin ang SOLID Principles sa Code - 2

SOLID na prinsipyo

Kaya paano mo isusulat ang SOLID code? Ito ay talagang simple. Kailangan mo lamang sundin ang limang panuntunang ito:
  • Prinsipyo ng Iisang Pananagutan
  • Bukas-sarado na prinsipyo
  • Prinsipyo ng pagpapalit ng Liskov
  • Prinsipyo ng paghihiwalay ng interface
  • Dependency Inversion Principle
Huwag kang mag-alala! Ang mga prinsipyong ito ay mas simple kaysa sa tila!

Prinsipyo ng Iisang Pananagutan

Sa kanyang aklat, inilarawan ni Robert C. Martin ang alituntuning ito tulad ng sumusunod: “Ang isang klase ay dapat magkaroon lamang ng isang dahilan para sa pagbabago.” Tingnan natin ang dalawang halimbawa nang magkasama.

1. Ano ang hindi dapat gawin

Mayroon kaming klase na tinatawag na User na nagpapahintulot sa user na gawin ang mga sumusunod na bagay:
  • Magrehistro ng isang account
  • Mag log in
  • Makatanggap ng notification sa unang pagkakataong mag-log in ka
Ang klase na ito ngayon ay may ilang mga responsibilidad. Kung magbabago ang proseso ng pagpaparehistro, magbabago ang klase ng User . Ganun din ang mangyayari kung magbabago ang proseso ng pag-login o proseso ng notification. Ibig sabihin, overloaded ang klase. Masyado siyang maraming responsibilidad. Ang pinakamadaling paraan upang ayusin ito ay ilipat ang responsibilidad sa iyong mga klase upang ang klase ng User ay responsable lamang sa pagsasama-sama ng mga klase. Kung magbabago ang proseso, mayroon kang isang malinaw at hiwalay na klase na kailangang baguhin.

2. Ano ang gagawin

Isipin ang isang klase na dapat magpakita ng notification sa isang bagong user, FirstUseNotification . Ito ay bubuo ng tatlong function:
  • Suriin kung ang isang abiso ay naipakita na
  • Ipakita ang abiso
  • Markahan ang notification bilang naipakita na
Marami bang dahilan ang klase na ito para magbago? Hindi. Ang klase na ito ay may isang malinaw na function - pagpapakita ng notification para sa isang bagong user. Nangangahulugan ito na ang klase ay may isang dahilan upang magbago. Ibig sabihin, kung magbabago ang layuning ito. Kaya, ang klase na ito ay hindi lumalabag sa nag-iisang prinsipyo ng responsibilidad. Siyempre, may ilang bagay na maaaring magbago: ang paraan ng pagmamarka ng mga notification bilang nabasa ay maaaring magbago, o ang paraan ng paglitaw ng notification. Gayunpaman, dahil malinaw at basic ang layunin ng klase, ayos lang ito.

Bukas-sarado na prinsipyo

Ang prinsipyong bukas-sarado ay nilikha ni Bertrand Meyer: "Ang mga bagay sa software (mga klase, module, function, atbp.) ay dapat na bukas para sa extension, ngunit sarado para sa pagbabago." Ang prinsipyong ito ay talagang napaka-simple. Dapat mong isulat ang iyong code upang ang mga bagong feature ay maidagdag dito nang hindi binabago ang source code. Nakakatulong ito na maiwasan ang isang sitwasyon kung saan kailangan mong baguhin ang mga klase na nakadepende sa iyong binagong klase. Gayunpaman, ang prinsipyong ito ay mas mahirap ipatupad. Iminungkahi ni Meyer ang paggamit ng mana. Ngunit ito ay humahantong sa isang malakas na koneksyon. Tatalakayin natin ito sa Mga Prinsipyo ng Interface Separation at ang Mga Prinsipyo ng Dependency Inversion. Kaya nakaisip si Martin ng isang mas mahusay na diskarte: gumamit ng polymorphism. Sa halip na maginoo na pamana, ang diskarteng ito ay gumagamit ng abstract base classes. Sa ganitong paraan, maaaring magamit muli ang mga detalye ng inheritance habang hindi kinakailangan ang pagpapatupad. Ang interface ay maaaring isulat nang isang beses at pagkatapos ay isara upang gumawa ng mga pagbabago. Dapat na ipatupad ng mga bagong function ang interface na ito at palawigin ito.

Prinsipyo ng pagpapalit ng Liskov

Ang prinsipyong ito ay naimbento ni Barbara Liskov, isang nagwagi ng Turing Award para sa kanyang mga kontribusyon sa mga programming language at software methodology. Sa kanyang artikulo, tinukoy niya ang kanyang prinsipyo tulad ng sumusunod: "Ang mga bagay sa isang programa ay dapat mapalitan ng mga pagkakataon ng kanilang mga subtype nang hindi naaapektuhan ang tamang pagpapatupad ng programa." Tingnan natin ang prinsipyong ito bilang isang programmer. Isipin na mayroon kaming isang parisukat. Maaaring ito ay isang parihaba, na parang lohikal dahil ang isang parisukat ay isang espesyal na hugis ng isang parihaba. Dito nagliligtas ang prinsipyo ng pagpapalit ng Liskov. Saanman mo inaasahan na makakita ng isang parihaba sa iyong code, posible ring lumitaw ang isang parisukat. Ngayon isipin na ang iyong parihaba ay may mga pamamaraan ng SetWidth at SetHeight . Nangangahulugan ito na kailangan din ng parisukat ang mga pamamaraang ito. Sa kasamaang palad, wala itong kabuluhan. Nangangahulugan ito na ang prinsipyo ng pagpapalit ng Liskov ay nilabag dito.

Prinsipyo ng paghihiwalay ng interface

Tulad ng lahat ng mga prinsipyo, ang prinsipyo ng paghihiwalay ng interface ay mas simple kaysa sa tila: "Maraming mga interface na partikular sa kliyente ay mas mahusay kaysa sa isang pangkalahatang layunin na interface." Tulad ng isang prinsipyo ng responsibilidad, ang layunin ay upang mabawasan ang mga side effect at ang bilang ng mga pagbabago na kinakailangan. Siyempre, walang sinuman ang nagsusulat ng ganoong code sa layunin. Pero madaling makaharap. Tandaan ang parisukat mula sa nakaraang prinsipyo? Ngayon isipin na nagpasya kaming ipatupad ang aming plano: gumagawa kami ng isang parisukat mula sa isang parihaba. Ngayon ay pinipilit namin ang parisukat na ipatupad ang setWidth at setHeight , na malamang na walang ginagawa. Kung ginawa nila iyon, malamang na may masisira kami dahil ang lapad at taas ay hindi tulad ng inaasahan namin. Sa kabutihang-palad para sa amin, nangangahulugan ito na hindi na namin nilalabag ang prinsipyo ng pagpapalit ng Liskov, dahil pinapayagan na namin ngayon ang paggamit ng isang parisukat saanman kami gumamit ng isang parihaba. Gayunpaman, lumilikha ito ng bagong problema: nilalabag namin ngayon ang prinsipyo ng paghihiwalay ng mga interface. Pinipilit namin ang nagmula na klase na ipatupad ang functionality na pinipili nitong huwag gamitin.

Dependency Inversion Principle

Ang huling prinsipyo ay simple: ang mga high-level na module ay dapat na magagamit muli at hindi dapat maapektuhan ng mga pagbabago sa mga low-level na module.
  • A. Ang mga high level na module ay hindi dapat nakadepende sa mga low level na modules. Parehong dapat nakadepende sa mga abstraction (tulad ng mga interface).
  • B. Ang mga abstraction ay hindi dapat nakadepende sa mga detalye. Ang mga detalye (kongkretong pagpapatupad) ay dapat nakadepende sa mga abstraction.
Ito ay maaaring makamit sa pamamagitan ng pagpapatupad ng abstraction na naghihiwalay sa mataas at mababang antas ng mga module. Ang pangalan ng prinsipyo ay nagpapahiwatig na ang direksyon ng dependence ay nagbabago, ngunit hindi ito ang kaso. Pinaghihiwalay lamang nito ang dependency sa pamamagitan ng pagpapakilala ng abstraction sa pagitan nila. Bilang resulta, makakakuha ka ng dalawang dependencies:
  • Mataas na antas ng module, depende sa abstraction
  • Mababang antas ng module depende sa parehong abstraction
Ito ay maaaring mukhang mahirap, ngunit sa katunayan ito ay awtomatikong nangyayari kung tama mong ilapat ang bukas/sarado na prinsipyo at ang Liskov substitution na prinsipyo. Iyon lang! Natutunan mo na ngayon ang limang pangunahing prinsipyo na nagpapatibay sa SOLID. Gamit ang limang prinsipyong ito, magagawa mong kamangha-mangha ang iyong code!
Mga komento
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION