Šimtakojų metodo sutramdymas: paprasto, prižiūrimo kodo rašymo strategijos
Kai buvau vaikas, gulėdavau ant lovos ir ilgai žiūrėdavau į seno sovietinio kilimėlio raštus, juose matydama gyvūnus ir fantastiškas figūras. Dabar dažniau žiūriu į kodą, bet galvoje vis dar kyla panašūs vaizdai. Kaip ir ant kilimėlio, šie vaizdai sudaro pasikartojančius raštus. Jie gali būti malonūs arba atstumti. Šiandien noriu papasakoti apie vieną tokį nemalonų modelį, kurį galima rasti programuojant.
Įsivaizduokite paslaugą, kuri apdoroja kliento registracijos užklausą ir siunčia apie tai įvykį Kafkai. Šiame straipsnyje pateiksiu įgyvendinimo pavyzdį, kurį laikau antipatternu, ir pasiūlysiu patobulintą versiją.
1 variantas: šimtakojų metodas
Žemiau pateiktas Java kodas rodo kodą RegistrationService
klasė, kuri apdoroja užklausą ir siunčia įvykį.
public class RegistrationService {
private final ClientRepository clientRepository;
private final KafkaTemplate<Object, Object> kafkaTemplate;
private final ObjectMapper objectMapper;
public void registerClient(RegistrationRequest request) {
var client = clientRepository.save(Client.builder()
.email(request.email())
.firstName(request.firstName())
.lastName(request.lastName())
.build());
sendEvent(client);
}
@SneakyThrows
private void sendEvent(Client client) {
var event = RegistrationEvent.builder()
.clientId(client.getId())
.email(client.getEmail())
.firstName(client.getFirstName())
.lastName(client.getLastName())
.build();
Message message = MessageBuilder
.withPayload(objectMapper.writeValueAsString(event))
.setHeader(KafkaHeaders.TOPIC, "topic-registration")
.setHeader(KafkaHeaders.KEY, client.getEmail())
.build();
kafkaTemplate.send(message).get();
}
@Builder
public record RegistrationEvent(int clientId, String email, String firstName, String lastName) {}
}
Kodo struktūrą galima supaprastinti taip:
Čia matote, kad metodai sudaro nenutrūkstamą grandinę, per kurią perduodami duomenys, kaip per ilgą, siaurą žarnyną. Šios grandinės viduryje esantys metodai yra atsakingi ne tik už logiką, tiesiogiai aprašytą jų turinyje, bet ir už metodų, kuriuos jie iškviečia, ir jų sutarčių logiką (pvz., poreikį tvarkyti konkrečias klaidas). Visi metodai, buvę prieš iškviestą, paveldi visą jo sudėtingumą. Pavyzdžiui, jei kafkaTemplate.send
turi šalutinį poveikį, kai siunčiamas įvykis, tada skambinama sendEvent
metodas taip pat įgauna tą patį šalutinį poveikį. The sendEvent
metodas taip pat tampa atsakingas už serializavimą, įskaitant jo klaidų tvarkymą. Atskirų kodo dalių testavimas tampa sudėtingesnis, nes nėra jokio būdo išbandyti kiekvieną dalį atskirai, nenaudojant maketų.
2 variantas: patobulinta versija
public class RegistrationService {
private final ClientRepository clientRepository;
private final KafkaTemplate<Object, Object> kafkaTemplate;
private final ObjectMapper objectMapper;
@SneakyThrows
public void registerClient(RegistrationController.RegistrationRequest request) {
var client = clientRepository.save(Client.builder()
.email(request.email())
.firstName(request.firstName())
.lastName(request.lastName())
.build());
Message<String> message = mapToEventMessage(client);
kafkaTemplate.send(message).get();
}
private Message<String> mapToEventMessage(Client client) throws JsonProcessingException {
var event = RegistrationEvent.builder()
.clientId(client.getId())
.email(client.getEmail())
.firstName(client.getFirstName())
.lastName(client.getLastName())
.build();
return MessageBuilder
.withPayload(objectMapper.writeValueAsString(event))
.setHeader(KafkaHeaders.TOPIC, "topic-registration")
.setHeader(KafkaHeaders.KEY, event.email)
.build();
}
@Builder
public record RegistrationEvent(int clientId, String email, String firstName, String lastName) {}
}
Diagrama parodyta žemiau:
Čia galite pamatyti, kad sendEvent
metodo visiškai nėra, ir kafkaTemplate.send
yra atsakingas už žinutės išsiuntimą. Visas pranešimo Kafkai kūrimo procesas buvo perkeltas į atskirą mapToEventMessage
metodas.
The mapToEventMessage
metodas neturi šalutinio poveikio, o jo atsakomybės ribos yra aiškiai apibrėžtos. Išimtys, susijusios su serializavimu ir pranešimų siuntimu, yra atskirų metodų sutarčių dalis ir gali būti tvarkomos atskirai.
The mapToEventMessage
metodas yra gryna funkcija. Kai funkcija yra deterministinė ir neturi šalutinio poveikio, vadiname „gryna“ funkcija. Grynosios funkcijos yra:
- lengviau skaityti,
- lengviau derinti,
- lengviau išbandyti,
- nepriklausomai nuo to, kokia tvarka jie vadinami,
- paprasta paleisti lygiagrečiai.
Rekomendacijos
Siūlyčiau šiuos metodus, kurie gali padėti išvengti tokių antipatternų kode:
- Trofėjų metodas
- Vienos krūvos technika
- Bandymu pagrįsta plėtra (TDD)
Visos šios technikos yra glaudžiai susijusios ir viena kitą papildo.
Testavimo trofėjus
Tai bandymo aprėpties metodas, kuriame akcentuojami integravimo testai, kuriais patikrinama visa paslaugos sutartis. Vienetų testai naudojami atskiroms funkcijoms, kurias sudėtinga arba brangu patikrinti atliekant integravimo testus. Šio metodo testus aprašiau savo straipsniuose: Veiksmingų integravimo testų rašymas pavasarį: organizuotos testavimo strategijos HTTP užklausų pasityčiojimui, integracijos testo skaidrumo gerinimas, patikimas testavimas naudojant Kafka: izoliavimo metodai.
Viena krūva
Ši technika aprašyta Kento Becko knygoje „Tidy First?” Pagrindinė mintis yra ta, kad skaityti ir suprasti kodą yra sunkiau nei jį rašyti. Jei kodas yra suskaidytas į per daug mažų dalių, gali būti naudinga pirmiausia jį sujungti į visumą, kad pamatytumėte bendrą struktūrą ir logiką, o tada vėl suskaidyti į suprantamesnes dalis.
Šio straipsnio kontekste siūloma neskaidyti kodo į metodus, kol jis neužtikrins reikiamos sutarties įvykdymo.
Bandymu pagrįstas kūrimas
Šis metodas leidžia atskirti kodo rašymo, siekiant įgyvendinti sutartį, ir kodo projektavimo pastangas. Mes nesistengiame vienu metu sukurti gero dizaino ir parašyti reikalavimus atitinkančio kodo, o šias užduotis atskiriame.
Kūrimo procesas atrodo taip:
- Parašykite paslaugų sutarties testus naudodami testavimo trofėjaus metodą.
- Parašykite kodą „One Pile“ stiliumi, užtikrindami, kad jis atitiktų reikiamą sutartį. Nesijaudinkite dėl kodo dizaino kokybės.
- Refaktorizuoti kodą. Visas kodas yra parašytas, o mes puikiai suprantame įgyvendinimą ir galimas kliūtis.
Išvada
Straipsnyje aptariamas antipatterno pavyzdys, dėl kurio gali kilti sunkumų prižiūrint ir išbandant kodą. Tokie metodai kaip „Testing Trophy“, „One Pile“ ir „Test-Driven Development“ leidžia struktūrizuoti savo darbą taip, kad kodas nepavirstų neįveikiamu labirintu. Investuodami laiką į tinkamą kodo organizavimą, padedame pagrindą mūsų programinės įrangos produktų ilgalaikiam tvarumui ir nesudėtingai priežiūrai.
Dėkojame už dėmesį straipsniui ir linkime sėkmės rašant paprastą kodą!