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.
Spis treści
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
1 2 3 4 5 6 7 |
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
1 2 3 4 5 6 7 8 |
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:
1 2 3 4 5 6 7 |
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:
1 2 3 4 5 6 |
/** * 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:
1 2 3 4 5 6 7 8 9 |
/* 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:
1 2 |
// this sets the students age function setStudentAge(); |
Mylące komentarze:
1 2 |
//this sets the fullname of the student function setLastName(); |
Śmieszny lub obraźliwy komentarz:
1 2 |
// 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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
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!