Pisanie ładnego, dobrze zaprojektowanego i dobrze wyglądającego kodu nie jest tak trudne, jak się wydaje. Wymaga trochę wysiłku, aby poznać główne zasady i po prostu używać ich w swoim kodzie. I nie musi to być wszystko na raz, ale jak poczujesz się komfortowo z jedną rzeczą, spróbuj pomyśleć o innej, i tak dalej.
Twórz solidny, a nie głupi kod
Zacznijmy od wprowadzenia najbardziej elementarnych zasad, które nazywane są SOLID. Jest to termin opisujący zbiór zasad projektowania dla dobrego kod który został wymyślony przez Roberta C. Martina i jak to działa:
S oznacza zasadę pojedynczej odpowiedzialności
Stwierdza ona, że klasa powinna mieć jeden i tylko jeden powód do zmiany. Innymi słowy, klasa powinna mieć tylko jedno zadanie do wykonania, więc powinniśmy unikać pisania dużych klas z wieloma obowiązkami. Jeśli nasza klasa ma robić wiele rzeczy, to każda z nich powinna zostać oddelegowana do osobnej.
klasa Powiadomienie
def initialize(params)
@params = params
end
def call
EmailSender.new(message).call
end
private
def wiadomość
# jakaś implementacja
koniec
end
O oznacza zasadę otwartą/zamkniętą
Stwierdza ona, że powinieneś być w stanie rozszerzyć zachowanie klasy bez modyfikowania jej. Innymi słowy, powinno być łatwo rozszerzyć klasę bez wprowadzania do niej jakichkolwiek modyfikacji. Możemy to osiągnąć np. za pomocą wzorca strategii lub dekoratorów.
klasa Powiadomienie
def initialize(params, sender)
@params = params
@sender = nadawca
end
def call
sender.call message
end
prywatny
def message
# jakaś implementacja
koniec
end
class EmailSender
def call(message)
# pewna implementacja
end
koniec
class SmsSender
def call(message)
# pewna implementacja
end
end
Dzięki takiemu rozwiązaniu możliwe jest dodawanie nowych nadawców bez konieczności zmiany kodu.
L oznacza zasadę substytucji Liskowa
Stwierdza ona, że klasy pochodne muszą być zastępowalne dla swoich klas bazowych. Innymi słowy, użycie klas pochodzących od tego samego przodka powinno być łatwe do zastąpienia przez innego potomka.
class Logger {
info (message) {
console.info(this._prefixFor('info') + message)
}
error (message, err) {
console.error(this._prefixFor('error') + message)
if (err) {
console.error(err)
}
}
_prefixFor (type) {
// jakaś implementacja
}
}
class ScepticLogger extends Logger {
info(message) {
super.info(message)
console.info(this._prefixFor('info') + 'I to wszystko, co miałem do powiedzenia.')
}
error (message, err) {
super.error(message, err)
console.error(this._prefixFor('error') + 'Wielka sprawa!')
}
}
Możemy łatwo zastąpić nazwę klasy, ponieważ obie mają dokładnie ten sam interfejs użytkowania.
Mam na myśli zasadę segregacji interfejsów
Stwierdza, że należy tworzyć drobnoziarniste interfejsy, które są specyficzne dla klienta. Czym jest interfejs? Jest to dostarczony sposób użycia pewnej części kodu. Tak więc naruszeniem tej zasady może być np. klasa ze zbyt wieloma metodami, a także metoda ze zbyt wieloma opcjami argumentów. Dobrym przykładem na zobrazowanie tej zasady jest wzorzec repozytorium, nie tylko dlatego, że często umieszczamy wiele metod w jednej klasie, ale także dlatego, że metody te są narażone na ryzyko przyjęcia zbyt wielu argumentów.
# nie jest tym razem najlepszym przykładem
class NotificationRepository
def find_all_by_ids(ids:, info:)
notifications = Notification.where(id: ids)
info ? notifications.where(type: :info) : notifications
end
end
# i jeszcze lepszy
class NotificationRepository
def find_all_by_ids(ids:)
Notification.where(id: ids)
end
def find_all_by_ids_info(ids:)
find_all_by_ids(ids).where(type: :info)
end
end
D oznacza zasadę inwersji zależności
Stwierdza, że powinieneś polegać na abstrakcjach, a nie na konkretyzacjach. Innymi słowy, klasa, która korzysta z innej, nie powinna zależeć od szczegółów jej implementacji, ważny jest tylko interfejs użytkownika.
klasa Powiadomienie
def initialize(params, sender)
@params = params
@sender = nadawca
end
def call
sender.call message
end
prywatny
def message
# jakaś implementacja
koniec
end
Wszystko co musimy wiedzieć o obiekcie nadawcy to to, że udostępnia on metodę `call`, która oczekuje wiadomości jako argumentu.
Nie jest to najlepszy kod w historii
Bardzo ważne jest również poznanie rzeczy, których należy bezwzględnie unikać podczas pisania kodu, więc oto kolejny zbiór STUPID zasad, które sprawiają, że kod nie jest łatwy w utrzymaniu, trudny do testowania i ponownego użycia.
S oznacza Singleton
Singletony są często uważane za anty-wzorce i generalnie powinno się ich unikać. Głównym problemem tego wzorca jest jednak to, że jest on swego rodzaju wymówką dla zmiennych/metod globalnych i może być szybko nadużywany przez programistów.
T oznacza szczelne złącze
Jest on zachowywany, gdy zmiana w jednym module wymaga również zmian w innych częściach aplikacji.
U oznacza Niesprawdzalność
Jeśli kod jest dobry, pisanie testów powinno być zabawą, a nie koszmarem.
P oznacza przedwczesną optymalizację
Słowo "przedwczesne" jest tutaj kluczowe, jeśli nie potrzebujesz tego teraz, to jest to strata czasu. Lepiej jest skupić się na dobrym, czystym kodzie niż na mikro-optymalizacjach - które generalnie powodują bardziej złożony kod.
Mam na myśli nieopisowe nazewnictwo
Jest to najtrudniejsza rzecz w pisaniu dobrego kodu, ale pamiętaj, że jest to nie tylko dla reszty twojego kodu. zespół ale także dla Ciebie w przyszłości, więc traktuj Cię dobrze 🙂 Lepiej jest napisać długą nazwę metody, która mówi wszystko, niż krótką i enigmatyczną.
D oznacza duplikację
Głównym powodem powielania kodu jest przestrzeganie zasady ścisłego sprzężenia. Jeśli twój kod jest ściśle powiązany, po prostu nie możesz go ponownie użyć i pojawia się zduplikowany kod, więc postępuj zgodnie z DRY i nie powtarzaj się.
W tej chwili nie ma to większego znaczenia
Chciałbym również wspomnieć o dwóch bardzo ważnych rzeczach, które często są pomijane. Pierwsza z nich to YAGNI, czyli: nie będziesz tego potrzebował. Od czasu do czasu obserwuję ten problem podczas przeglądania kodu lub nawet pisania własnego kodu, ale powinniśmy zmienić nasze myślenie o implementacji funkcji. Powinniśmy pisać dokładnie taki kod, jaki jest nam potrzebny w danym momencie, ani mniej, ani więcej. Powinniśmy pamiętać, że wszystko zmienia się bardzo szybko (zwłaszcza wymagania aplikacji), więc nie ma sensu myśleć, że coś kiedyś się przyda. Nie marnujmy czasu.
Nie rozumiem
I ostatnia rzecz, chyba nie do końca oczywista, a dla niektórych może być dość kontrowersyjna, to kod opisowy. Nie mam tu na myśli tylko używania odpowiednich nazw dla klas, zmiennych czy metod. Naprawdę dobrze jest, gdy cały kod jest czytelny od pierwszego wejrzenia. Jaki jest cel bardzo krótkiego kodu, który jest tak enigmatyczny, jak to tylko możliwe i nikt nie wie, co robi, z wyjątkiem osoby, która go napisała? Moim zdaniem lepiej jest napisać kilka znakówdeklaracje warunkówcoś więcej niż jedno słowo, a potem wczoraj siedzenie i zastanawianie się: czekaj, jaki jest wynik, jak to się stało i tak dalej.
const params = [
{
movies: [
{ title: 'Skazani na Shawshank' }
{ title: 'Lot nad kukułczym gniazdem' }
]
},
{
filmy: [
{ title: 'Szeregowiec Ryan' }
{ title: 'Pulp Fiction' }
{ tytuł: "Skazani na Shawshank" },
]
}
]
// pierwsza propozycja
function uniqueMovieTitlesFrom (params) {
const titles = params
.map(param => param.movies)
.reduce((prev, nex) => prev.concat(next))
.map(movie => movie.title)
return [...new Set(titles)]
}
// druga propozycja
function uniqueMovieTitlesFrom (params) {
const titles = {}
params.forEach(param => {
param.movies.forEach(movie => titles[movie.title] = true)
})
return Object.keys(titles)
}
Podsumowując
Jak widać, istnieje wiele zasad do zapamiętania, ale jak wspomniałem na początku, pisanie ładnego kodu jest kwestią czasu. Jeśli zaczniesz myśleć o jednym ulepszeniu swoich nawyków kodowania, zobaczysz, że pojawią się kolejne dobre zasady, ponieważ wszystkie dobre rzeczy powstają same z siebie, tak jak te złe.
Czytaj więcej:
Czym jest Ruby on Jets i jak zbudować aplikację przy jego użyciu?
Vuelendar. Nowy projekt Codest oparty na Vue.js
Cotygodniowy raport Codest z najlepszymi artykułami technicznymi. Tworzenie oprogramowania dla 50 milionów współbieżnych gniazd (10)