Dzisiaj przedstawię Ci najczęstsze błędy związane z używaniem instrukcji warunkowych: if, else-if oraz else. Możesz się zdziwić, ale prawdopodobnie, po przeczytaniu tego wpisu będziesz w stanie poprawić czytelność i zmniejszyć złożoność swojego kodu.
Poprawne stosowanie instrukcji warunkowych
Dzisiejszy wpis był początkowo planowany na nieco późniejszy termin. Jednak z uwagi na fakt narastającej we mnie frustracji, postanowiłem napisać go szybciej. Skąd owa frustracja? Zauważyłem, że za każdym razem, kiedy dostaję juniora pod swoje skrzydła, to popełnia on te same błędy, związane z instrukcjami warunkowymi, co inni. Nie wiem skąd się to bierze, jednak powtarza się to, jak mantra. Wychodzę więc naprzeciw oczekiwaniom stawianym względem mnie i oddaję w Wasze ręce ten krótki, lecz mam nadzieję treściwy wpis.
Instrukcja if-else if-else
Możesz sobie pomyśleć: jakież to błędy można popełnić stosując jedną z najbardziej podstawowych instrukcji w programowaniu? Otóż można i to dosyć poważne… Spójrz na poniższy przykład. Mamy tutaj prostą funkcję, która ma za zadanie zwalidować dane użytkownika, sprawdzić czy adres email jest unikatowy, a następnie go stworzyć oraz zwrócić id nowo utworzonego użytkownika.
async createUser(createUserDto: CreateUserDto): Promise<UserId> {
if (!Validator.isValid(createUserDto, CreateUserDtoValidationSchema)) {
if (!(await this.userRepository.findOne({ email: createUserDto.email }))) {
const user = await this.userRepository.save(User.create(createUserDto));
if (user) {
await this.eventBus.dispatch(new UserCreated(user));
return user.id;
} else {
throw new Error('User has not been created.');
}
} else {
throw new Error('Selected email address is already in use.');
}
} else {
throw new Error('Provided user data is incorrect.');
}
}
Pomińmy kwestie związane z regułami clean code oraz dobrymi praktykami i skupmy się tylko na instrukcjach warunkowych tej funkcji.
Po co te else’y?
Pierwszą kwestią, którą chcę omówić są else’y. Zawsze powtarzam, że jeśli musisz użyć else’a, to prawdopodobnie znaczy, że Twój kod można poprawić. Przecież możemy zmodyfikować nasze warunki, odwracając operację logiczną i pozbyć się instrukcji else. Spójrzmy, jak nasz kod będzie wyglądał bez instrukcji else, jeśli zmodyfikujemy warunki naszych if-ów.
async createUser(createUserDto: CreateUserDto): Promise<UserId> {
if (Validator.isValid(createUserDto, CreateUserDtoValidationSchema)) {
if (!(await this.userRepository.findOne({ email: createUserDto.email }))) {
const user = await this.userRepository.save(User.create(createUserDto);
if (user)) {
await this.eventBus.dispatch(new UserCreated(user));
return user.id;
}
throw new Error('User has not been created.');
}
throw new Error('Selected email address is already in use.');
}
throw new Error('Provided user data is incorrect.');
}
Ubyło nam troszkę kodu i moim zdaniem stał się on ciut ładniejszy na pierwszy rzut oka.
Nie zagnieżdżaj if-ów
Kolejna rzecz, a raczej niepisana zasada clean code: nie zagnieżdżaj instrukcji warunkowych! Każde kolejne zagnieżdżenie generuje dodatkową złożoność kodu, ponieważ osoba, która czyta ten kod musi analizować poszczególne przypadki. Gdybyśmy nie umieszczali jednego if-a w drugim, to nasz kod czytałoby się od góry do dołu, a nie, tak jak w tym przypadku: od góry w bok.
async createUser(createUserDto: CreateUserDto): Promise<UserId> {
if (!Validator.isValid(createUserDto, CreateUserDtoValidationSchema)) {
throw new Error('Provided user data is incorrect.');
}
if (await this.userRepository.findOne({ email: createUserDto.email })) {
throw new Error('Selected email address is already in use.');
}
const user = await this.userRepository.save(User.create(createUserDto);
if (user) {
await this.eventBus.dispatch(new UserCreated(user));
return user.id;
}
throw new Error('User has not been created.');
}
Maksymalnie zagnieżdżenie funkcji createUser, to aktualnie jeden. Jej kod czytamy teraz od góry do dołu i nie musimy poświęcać aż tyle czasu na analizę tych części, które „szły w bok”.
If-y szybkiego reagowania wyżej
Ostatnia rada na dziś: staraj się najpierw sprawdzać warunki, których skutkiem ubocznym jest wyjątek, bądź zwrócenie rezultatu. Ujmując to inaczej, wyjdź z metody tak szybko, jak jest to możliwe oraz do ciała if-ów wrzucaj jak najmniej kodu. Zmodyfikujmy ostatnią instrukcję warunkową, tak, aby sprawdzała czy nie udało się stworzyć użytkownika, a następnie zwróćmy wyjątek w if-ie.
async createUser(createUserDto: CreateUserDto): Promise<UserId> {
if (!Validator.isValid(createUserDto, CreateUserDtoValidationSchema)) {
throw new Error('Provided user data is incorrect.');
}
if (await this.userRepository.findOne({ email: createUserDto.email })) {
throw new Error('Selected email address is already in use.');
}
const user = await this.userRepository.save(User.create(createUserDto);
if (!user) {
throw new Error('User has not been created.');
}
await this.eventBus.dispatch(new UserCreated(user));
return user.id;
}
Podsumowanie
- Unikaj instrukcji else, ponieważ w większości przypadków nie są one potrzebne.
- Nie zagnieżdżaj if-ów, bo każde zagnieżdżenie komplikuje Twój kod.
- Wychodź z metody tak szybko, jak to możliwe.
Dodaj komentarz