Jak pisać czysty kod? Poradnik dla junior developera
Pisanie kodu to jedno, ale pisanie czystego, czytelnego kodu, to zupełnie co innego. Jednakże czym właściwie jest „czysty kod”? Stworzyłem ten krótki poradnik pisania czystego kodu dla początkujących, by pomóc wam w zrozumieniu i doskonałym opanowaniu sztuki pisania czystego kodu.
Chris Blakely. Senior Software Engineer w Dawson Andrews. Jego misją jest pomoc młodym i początkującym programistom w ich podróży poprzez tajniki tworzenia dobrego kodu. Uwielbia pisać, tworzyć tutoriale i udzielać się w rozwijaniu projektów open source. Jako doświadczony programista niejednokrotnie prowadził mentoringi i wdrożenia dla młodszych pracowników swoich teamów.
Wyobraź sobie, że czytasz artykuł. Nie ma wstępu, który daje ci ogólne pojęcie, o czym jest tekst. Są nagłówki, każdy z kilkoma akapitami, akapity uporządkowane odpowiednimi kawałkami informacji, zgrupowanymi w takiej kolejności, że tekst „płynie” i dobrze się go czyta.
Teraz wyobraź sobie artykuł bez żadnego wstępu. Są akapity, ale długie i w dezorientującej kolejności. Nie możesz po prostu przejrzeć artykułu, musisz naprawdę zagłębić się w jego treść, aby dowiedzieć się, o czym jest. Pewnie domyślasz się, że czytanie tak sformatowanego tekstu może być frustrujące.
Twój kod powinien być czytelny jak dobry artykuł. Pomyśl o klasach/plikach jak o nagłówkach, a o swoich metodach jak o akapitach. Zdania są instrukcjami w twoim kodzie. Tutaj znajdziesz kilka cech czystego kodu:
- Czysty kod jest skupiony. Każda funkcja, każda klasa i moduł powinny robić jedną rzecz i robić ją dobrze.
- Powinien być elegancki. Czysty kod powinien być prosty do przeczytania. Czytanie go powinno wręcz wywoływać uśmiech na twojej twarzy. Powinien pozostawić cię z myślą: „Wiem doskonale, co ten kod robi”.
- Czysty kod jest zadbany. Ktoś poświęcił swój czas, aby uczynić go prostym i uporządkowanym. Zwrócił należytą uwagę na szczegóły. Zależało mu.
- Testy powinny przejść pomyślnie. Wadliwy kod nie jest czysty!
Przechodząc do najważniejszego pytania — jak właściwie pisać czysty kod jako junior developer? Przedstawię moje najważniejsze wskazówki na początek.
Używaj spójnego formatowania i wcięć
Książki byłyby ciężkie do czytania, gdyby odstępy między wierszami były niespójne, rozmiary czcionek — różne, a podział wiersza był gdzie popadnie. To samo tyczy się twojego kodu.
Aby uczynić swój kod był przejrzystym i czytelnym, upewnij się, że wcięcia, podziały wierszy i formatowanie są spójne. Oto dobry i zły przykład:
Przykład dobrego formatowania
function getStudents(id) { if (id !== null) { go_and_get_the_student(); } else { abort_mission(); } }
- Już na pierwszy rzut oka możesz stwierdzić, że w funkcji znajduje się instrukcja if/else.
- Nawiasy klamrowe i spójne wcięcia ułatwiają sprawdzenie, gdzie zaczynają się i kończą bloki kodu.
- Nawiasy klamrowe są spójne — zauważ, że nawias otwierający dla function i dla if są w tej samej linii.
Przykład złego formatowania
function getStudents(id) { if (id !== null) { go_and_get_the_student();} else { abort_mission(); } }
Rety! Co tu się zadziało!
- Wcięcie jest zrobione byle gdzie — ciężko powiedzieć, gdzie kończy się funkcja, a gdzie zaczyna blok
if/else
(tak, jest tam taki blok). - Nawiasy są dezorientujące i niespójne.
- Odstępy między wierszami są niespójne.
To trochę przesadzony przykład, ale dobrze pokazuje korzyści płynące ze stosowania spójnego wcięcia i formatowania. Nie wiem jak dla ciebie, ale dla mnie „dobry” przykład formatowania był o wiele czytelniejszy!
Dobre wiadomości są takie, że jest bardzo dużo wtyczek IDE których możesz użyć do automatycznego formatowania kodu. Alleluja!
- VS Code: Prettier
- Atom: Atom Beautify
- Sublime Text: Prettify
Użyj jasnych nazw zmiennych i metod
Na początku mówiłem o tym, jak ważne jest, aby twój kod był łatwy do odczytania. Ważnym tego aspektem jest wybór nazwy (jest to jeden z błędów, które popełniłem, gdy byłem junior developerem). Spójrzmy na przykład dobrego nazewnictwa:
function changeStudentLabelText(studentId){ const studentNameLabel = getStudentName(studentId); } function getStudentName(studentId){ const student = api.getStudentById(studentId); return student.name; }
Ten fragment kodu jest dobry z kilku powodów:
- Funkcje są jasno określone za pomocą dobrze nazwanych argumentów. Gdy developer je odczytuje, jest dla niego jasne, że „jeśli przywoła metodę
getStudentName()
zestudentId
, otrzyma z powrotem imię studenta” – nie muszą nawigować do metodygetStudentName()
jeśli nie mają potrzeby. - W metodzie
getStudentName()
, znowu – zmienne i nazwy metod są konkretnie nazwane – łatwo zauważyć, że metody wywołująapi
, otrzymuje obiektstudent
i zwraca własnośćname
. Prościzna!
Wybieranie dobrych nazw przy pisaniu czystego kodu jako początkujący jest trudniejsze, niż myślisz. W miarę rozwoju aplikacji wykorzystuj następujące konwencje, aby upewnić się, że twój kod jest czytelny:
- Wybierz konkretny styl nazywania i bądź konsekwentny. Używaj albo
camelCase
, albounder_scores
, ale nigdy obydwu naraz! - Nazwij swoje funkcje, metody i zmienne według ich działania lub według tego, czym są. Jeśli twoje metody coś uzyskują, umieść
get
w nazwie. Jeśli twoja zmienna przechowuje kolor samochodu, nazwij ją przykładowocarColour
.
DODATKOWA WSKAZÓWKA — jeśli nie możesz konkretnie nazwać swojej funkcji lub metody, oznacza to, że robi ona zbyt wiele. Śmiało podziel je na mniejsze funkcje! Dla przykładu, jeśli skończysz z funkcją o nazwie updateCarAndSave()
, stwórz z niej dwie oddzielne metody, updateCar()
i saveCar()
.
Używaj komentarzy tam, gdzie to konieczne
Mówi się, że „kod powinien być samodokumentujący się”, co w zasadzie oznacza, że twój kod powinien być na tyle czytelny, aby nie było potrzeby stosowania komentarzy. To istotne stwierdzenie i podejrzewam, że ma sens w idealnym świecie. Jednak świat kodowania jest daleki od ideału, dlatego komentarze są czasem konieczne.
Komentarze dokumentacji opisują, co dokładnie robi konkretna funkcja lub klasa. Jeśli piszesz bibliotekę, będzie to użyteczne dla korzystających z niej developerów. Oto przykład z useJSDoc:
/** * Solves equations of the form a * x = b * @example * // returns 2 * globalNS.method1(5, 10); * @example * // returns 3 * globalNS.method(5, 15); * @returns {Number} Returns the value of x for the equation. */ globalNS.method1 = function (a, b) { return b / a; };
Komentarze objaśniające są przeznaczone dla każdego (włączając ciebie w przyszłości), kto będzie potrzebował utrzymać, refaktoryzować bądź rozszerzyć swój kod. Najczęściej komentarzy objaśniających można uniknąć na korzyść „samodokumentującego się kodu”. Oto przykład takiego komentarza:
/* This function calls a third party API. Due to some issue with the API vender, the response returns "BAD REQUEST" at times. If it does, we need to retry */ function getImageLinks(){ const imageLinks = makeApiCall(); if(imageLinks === null){ retryApiCall(); } else { doSomeOtherStuff(); } }
Tutaj jest kilka komentarzy, których powinieneś spróbować unikać. Nie oferują zbyt dużej wartości, mogą być mylące i zwyczajnie zaśmiecające kod.
Zbędne komentarze niedające wartości:
// this sets the students age function setStudentAge();
Mylące komentarze:
//this sets the fullname of the student function setLastName();
Śmieszny lub obraźliwy komentarz:
// this method is 5000 lines long but it's impossible to refactor so don't try function reallyLongFunction();
Pamiętaj o zasadzie DRY (Don’t Repeat Yourself, czyli nie powtarzaj się)
Zasada DRY brzmi:
„Każda cząstka wiedzy musi posiadać pojedynczą, jednoznaczną, autorytatywną reprezentację w systemie.”
Na najprostszym poziomie, oznacza to, że powinieneś dążyć do redukcji ilości istniejącego już zduplikowanego kodu. (Zauważ, że użyłem słowa „redukcja”, a nie „eliminacja” — w niektórych przypadkach zduplikowany kod to nie koniec świata!)
Zduplikowany kod może być koszmarny do utrzymania i modyfikacji. Spójrzmy na ten przykład:
function addEmployee(){ // create the user object and give the role const user = { firstName: 'Rory', lastName: 'Millar', role: 'Admin' } // add the new user to the database - and log out the response or error axios.post('/user', user) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); }); } function addManager(){ // create the user object and give the role const user = { firstName: 'James', lastName: 'Marley', role: 'Admin' } // add the new user to the database - and log out the response or error axios.post('/user', user) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); }); } function addAdmin(){ // create the user object and give the role const user = { firstName: 'Gary', lastName: 'Judge', role: 'Admin' } // add the new user to the database - and log out the response or error axios.post('/user', user) .then(function (response) { console.log(response); }) .catch(function (error) { console.log(error); }); }
Wyobraź sobie, że tworzysz dla klienta aplikację do zarządzania zasobami ludzkimi. Aplikacja pozwala adminom dodawać użytkowników wraz z ich rolami do bazy danych przez API. Dostępne są trzy role; pracownika, managera i admina. Spójrzmy na niektóre funkcje, które mogą zaistnieć:
Super! Kod działa i wszystko jest dobrze na świecie. Po chwili nasz klient przychodzi i mówi:
Hej! Chcielibyśmy, aby wyświetlany komunikat o błędzie zawierał zdanie „wystąpił błąd”. Co więcej, żeby być bardziej wkurzający, chcemy zmienić endpoint API z/user
na/users
. Dzięki!
Zanim więc wskoczymy w wir kodowania, zróbmy krok w tył. Pamiętasz, jak na początku tego artykułu powiedziałem, że „Czysty kod powinien być skupiony”? Aby zrobić jedną rzecz, ale zrobić ją dobrze? To moment, w którym nasz obecny kod napotyka małą przeszkodę. Kod, który sprawia, że API przywołuje i obsługuje błąd, jest powtórzony — co oznacza, że musimy zmienić kod w trzech miejscach, aby spełnić nowe wymagania. To irytujące!Pisanie kodu to jedno, ale pisanie czystego, czytelnego kodu, to zupełnie co innego. Jednakże czym właściwie jest „czysty kod”? Stworzyłem ten krótki poradnik pisania czystego kodu dla początkujących, by pomóc wam w zrozumieniu i doskonałym opanowaniu sztuki pisania czystego kodu.
A więc, co jeśli zrefaktorujemy kod na bardziej skupiony? Spójrzmy na poniższy przykład:
function addEmployee(){ // create the user object and give the role const user = { firstName: 'Rory', lastName: 'Millar', role: 'Admin' } // add the new user to the database - and log out the response or error saveUserToDatabase(user); } function addManager(){ // create the user object and give the role const user = { firstName: 'James', lastName: 'Marley', role: 'Admin' } // add the new user to the database - and log out the response or error saveUserToDatabase(user); } function addAdmin(){ // create the user object and give the role const user = { firstName: 'Gary', lastName: 'Judge', role: 'Admin' } // add the new user to the database - and log out the response or error saveUserToDatabase(user); } function saveUserToDatabase(user){ axios.post('/users', user) .then(function (response) { console.log(response); }) .catch(function (error) { console.log("there was an error " + error); }); }
Przenieśliśmy logikę tworzącą wywołanie API do naszej własnej metody saveUserToDatabase(user)
(czy to dobra nazwa? Sam zdecyduj!), którą inne metody będą wywoływać, aby zapisać użytkownika. Teraz, jeśli potrzebujemy zmienić znowu logikę API, musimy tylko zaktualizować jedną metodę. Tak samo, jeśli musimy dodać kolejną metodę, która tworzy użytkowników, to metoda zapisywania użytkowników w bazie danych za pośrednictwem api już istnieje. Hurra!
Przykład refaktoringu korzystający z tego, czego do tej pory się nauczyliśmy
Zamknijmy oczy i usilnie udawajmy, że robimy aplikację kalkulatora. Wykorzystywane są funkcje, które pozwalają nam odpowiednio na dodawanie, odejmowanie, mnożenie i dzielenie. Wynik jest wysyłany do konsoli.
Oto, co uzyskaliśmy do tej pory. Sprawdź, czy możesz dostrzec problemy, zanim przejdziemy dalej:
function addNumbers(number1, number2) { const result = number1 + number2; const output = 'The result is ' + result; console.log(output); } // this function substracts 2 numbers function substractNumbers(number1, number2){ //store the result in a variable called result const result = number1 - number2; const output = 'The result is ' + result; console.log(output); } function doStuffWithNumbers(number1, number2){ const result = number1 * number2; const output = 'The result is ' + result; console.log(output); } function divideNumbers(x, y){ const result = number1 / number2; const output = 'The result is ' + result; console.log(output); }
Co jest problemem?
- Wcięcie jest niespójne – nie ma za bardzo znaczenia, jakiego formatu wcięcia użyjemy, tak długo, jak jest ono spójne dla całego kodu
- Druga funkcja ma kilka zbędnych komentarzy – możemy powiedzieć, o co chodzi po przeczytaniu nazwy funkcji i samego kodu bez funkcji, więc czy naprawdę potrzebujemy tutaj komentarza?
- Trzecia i czwarta funkcja zostały źle nazwane –
doStuffWithNumbers()
nie jest najlepszą nazwą funkcji, jako że nie określa, co ta funkcja dokładnie robi. Zmienne(x, y)
również nie są zbyt klarowne – czyx & y
są funkcjami? Numerami? Bananami? - Metody wykonują więcej niż jedną czynność – wykonują obliczenia, ale także wyświetlają dane wyjściowe. Możemy rozdzielić logikę wyświetlania na osobne metody – zgodnie z zasadą DRY
Teraz wykorzystamy to, czego nauczyliśmy się z tego poradnika, do takiej refaktoryzacji, aby nasz nowy kod wyglądał następująco:
function addNumbers(number1, number2){ const result = number1 + number2; displayOutput(result) } function substractNumbers(number1, number2){ const result = number1 - number2; displayOutput(result) } function multiplyNumbers(number1, number2){ const result = number1 * number2; displayOutput(result) } function divideNumbers(number1, number2){ const result = number1 * number2; displayOutput(result) } function displayOutput(result){ const output = 'The result is ' + result; console.log(output); }
- Poprawiliśmy wcięcie tak, aby było spójne
- Zmodyfikowaliśmy nazewnictwo funkcji i zmiennych
- Usunęliśmy niepotrzebne komentarze
- Przenieśliśmy logikę
displayOutput()
do jej własnej metody — jeśli zapis danych potrzebuje zmiany, musimy zmienić go tylko w jednym miejscu
Gratulacje! Teraz możesz mówić, że znasz zasady pisania czystego kodu podczas rozmów rekrutacyjnych i przy pisaniu swojego zabójczo dobrego CV!
Nie „przeczyść” swojego kodu
Często spotykam developerów, którzy ciągle czyszczą swój kod. Uważaj, aby nie czyścić swojego kodu za bardzo, bo może to przynieść odwrotny efekt, i wręcz uczynić kod trudniejszym do przeczytania i utrzymania. Może również mieć wpływ na produktywność, jeśli developer ciągle przeskakuje pomiędzy zbyt wieloma plikami/metodami, aby dokonać prostej zmiany.
Bądź świadomy czystego kodu, ale nie myśl o nim za dużo w początkowych fazach twojego projektu. Upewnij się, że twój kod działa i jest dobrze przetestowany. Dopiero podczas etapu refaktoryzacji powinieneś myśleć o tym, jak wyczyścić swój kod, używając zasady DRY itp.
W tym przewodniku pisania czystego kodu, nauczyliśmy się, jak:
- Używać spójnego formatowania i wcięć.
- Używać jasnych i zrozumiałych nazw zmiennych i metod.
- Używać komentarzy, kiedy jest to konieczne.
- Używać zasady DRY (Don’t Repeat Yourself, czyli nie powtarzaj się).
Jeśli podobał ci się ten przewodnik, sprawdź koniecznie Clean Code: A Handbook of Agile Software Craftsmanship Roberta C. Martina. Jeśli poważnie myślisz o pisaniu czystego kodu i wyjściu z poziomu junior developera, gorąco polecam tę książkę.
Dzięki za lekturę!
Aby otrzymać najnowsze przewodniki i kursy dla junior developerów prosto na swoją skrzynkę mailową, dołącz do listy mailingowej na stronie: www.chrisblakely.dev.
Artykuł został przetłumaczony za zgodą autora. Autorką tłumaczenia jest Kinga Bielicka. Zdjęcie główne artykułu pochodzi z unsplash.com.