Skocz do treści

3. Weekly JavaScript Challenge

17

Zakończył się trzeci Weekly JavaScript Challenge. Grupa zyskuje co raz większą popularność, zapisały się do niej już 494 osoby. Dziękuję wszystkim, którzy rozwiązują zadania i komentują pod rozwiązaniami innych osób!

Zdjęcie Michał Miszczyszyn
Dobry Kod3 komentarze

Weekly JavaScript Challenge to grupa na Facebooku. Celem jej istnienia jest wspólna nauka JavaScriptu poprzez rozwiązywanie prostych zadań i wzajemną ocenę kodu.

Pamiętajcie, że poprzednie zadania można rozwiązywać przez cały czas!

Podsumowanie

Tymczasem, niemal już tradycyjnie, przejdę do podsumowania problemów, które szczególnie zapamiętałem z tego zadania. Są to ogólne dobre praktyki i porady odnośnie pisania kodu w JavaScripcie. Zapraszam do czytania.

Konwencje nazywania zmiennych i funkcji w JavaScript

Praktycznie nikt nie dyskutuje już z pewnymi ustalonymi konwencjami nazewnictwa w JavaScript. Podążają za nim zarówno funkcje standardowe, jak i większość frameworków i bibliotek. Oto one:

  • praktycznie nieużywana jest konwencja nazewnictwa underscorefirst_name, my_application itp.
  • zwyczajowo używa się nazewnictwa camelCasefirstName, myApplication itp. nazwy zaczynają się z małej litery
  • wyjątkiem są nazwy klas i konstruktorów, które zaczynają się wielką literą – User, MyModel itp. jest to tzw. PascalCase

Dodatkowo nieużywana jest tzw. notacja węgierska: nazwy zmiennych nie powinny zawierać informacji o typach. Przykładowo ageNum, nameString są niezalecane.

Współdzielony stan

Oczywiście musimy brać poprawkę na to, że niektóre z porad, które tutaj opisuję mogą się wydawać irracjonalne w kontekście prostego zadania i kodu, który ma maksymalnie 100-200 linijek. Jednak przy bardziej rozbudowanych aplikacjach wszystko to zaczyna mieć znacznie większy sens.

Bardzo często we wrzucanych rozwiązaniach widzę kilka zmiennych zadeklarowanych na samym początku kodu, a następnie różne funkcje, które korzystają z tych zmiennych. To podejście działa przy krótkim, prostym kodzie, jednak w większych aplikacjach szybko zaczyna być bardzo problematyczne w dalszym rozwoju i utrzymaniu. Dlatego, poza pewnymi wyjątkami, starajmy się unikać takich sytuacji, w których uzyskujemy dostęp do tej samej globalnej zmiennej z kilku różnych funkcji.

Różne poziomy abstrakcji

Złym pomysłem jest tworzenie funkcji, które niby wykonują jedno zadanie, ale operują na różnych poziomach abstrakcji. Przykładowo:

function saveData() {  
    localStorage.setItem('time', new Date();
    saveForm();
}

function saveForm() {  
    for (const input of inputs) {
        localStorage.setItem(input.name, input.value);
    }
}

Funkcja saveData operuje na dwóch poziomach abstrakcji – najpierw bezpośrednio zapisuje dane do localStorage, a później wywołuje inną funkcję, która również zapisuje dane do localStorage. Lepszym rozwiązaniem jest tworzenie funkcji, które działają tylko na jednym poziomie:

function saveData() {  
    saveTime();
    saveForm();
}

function saveTime() {  
    localStorage.setItem('time', new Date();
}

function saveForm() {  
    for (const input of inputs) {
        localStorage.setItem(input.name, input.value);
    }
}

Długie wyrażenia

Niejednokrotnie w kodzie zdarzają się długie i skomplikowane wyrażenia regularne lub warunki przekazywane do if. Dla poprawy czytelności należałoby wynieść je do osobnych zmiennych i dobrze nazwać. Przykładowo, autentyczne przykłady:

update(key, value) {  
    return (typeof value === 'object' ? Object.assign({}, this.data[key], value) : value);
}getGroup(name) {  
    return name.match(/^(\w+)(?:-(\w+))?$/);
}

setChecked(data, input) {  
    data.checked = input.name === data.name;
}

Mało czytelne. Poprawiona wersja wygląda następująco:

update(key, value) {  
    const isObject = (typeof value === 'object');
    if (isObject) {
        return this._newValueForObject(value);
    }
    return value;
}

_newValueForObject(key, value) {  
    return Object.assign({}, this.data[key], value);
}getGroup(name) {  
    const groupPattern = /^(\w+)(?:-(\w+))?$/;
    return name.match(groupPattern);
}

setChecked(data, input) {  
    const isChecked = (input.name === data.name);
    data.checked = isChecked;
}

querySelectorAll nie zwraca tablicy

Dla wielu osób zaskoczeniem jest fakt, że funkcja document.querySelectorAll nie zwraca tablicy, tylko pewien obiekt „tablicopodobny”:

const divs = document.querySelectorAll('div');  
Array.isArray(divs); // false!  

Kilka osób skorzystało ze znalezionego w internecie starszego rozwiązania tego problemu:

const divs = [].slice.call(document.querySelectorAll('div'));  
Array.isArray(divs); // true!  

Jednak jest to kod niepotrzebnie długi i na pewno bardzo nieczytelny. Znacznie lepiej skorzystać z funkcji Array.from:

const divs = Array.from(document.querySelectorAll('div'));  
Array.isArray(divs); // true!  

keyup

Jeśli chcemy wykonać jakieś akcje w czasie wpisywania tekstu w pole tekstowe, jedną z możliwości jest nasłuchiwanie na zdarzenie keyup. Pozornie to rozwiązanie zadziała, gdy użytkownik będzie korzystał wyłącznie z klawiatury – jednak przecież to nie jedyny sposób edytowania tekstu na stronie internetowej. Można również skorzystać z menu kontekstowego albo klawiatury ekranowej – a tych nasłuchiwanie na zdarzenie keyup już nie złapie.

Rozwiązanie? Zdarzenie input.

Niestandardowe funkcje

Przeszukując internet można natknąć się na wiele porad wykorzystujących niestandardowe funkcje w JavaScripcie. Przykładem mogą być date.toLocaleFormat() czy Array.forEach. Obie te funkcje nie są częścią żadnego standardu i nie można polegać na tym, że będą dostępne w przeglądarkach.

Funkcjonalności

Muszę jeszcze wspomnieć o jednej rzeczy. Określenie „funkcjonalności” jest kalką językową z angielskiego (functionalities) i nie istnieje w języku polskim. Nieprawidłowym jest więc powiedzieć „aplikacja ma wiele funkcjonalności” – zamiast tego prawidłowe jest „aplikacja ma wiele funkcji”. Po prostu :) Funkcjonalność to cecha i oznacza dobre spełnianie swoich funkcji.

Na koniec

Ponownie zachęcam do wzięcia udziału w Weekly JavaScript Challenge. Kolejne, czwarte zadanie dotyczy komunikacji z prostym REST API (Giphy.com). Zapraszam!

👉  Znalazłeś/aś błąd?  👈
Edytuj ten wpis na GitHubie!

Autor