Do’s and dont’s w programowaniu aplikacji (webowych)
Widziałem w życiu kod wielu aplikacji, deployowałem jeszcze więcej, sporo “debugowałem” (tzn. szukanie czemu to %&*# nie działa mimo że gdzie indziej na “prawie” takiej samej konfiguracji działa), parę napisałem i jest parę rzeczy które wkurzają mnie jako admina, nie mówiąc już o zmarnowanym czasie.
Nie jestem ekspertem jezeli chodzi o pisanie aplikacji (“ekspertowanie się” jest niestety popularne w internecie “pisałem 5 lat koszmarne aplikacje to zaczne blogowac jakim to jestem awsom ekspertem w PHPie i uczyć ludzi pisać jeszcze gorsze aplikacje”), ale koszmaryzm deployowania i zarządzania niektórymi appami doprowadził do tej listy ;]
DONT’s** **
HTACCESS!!!
Tak wiem że jest to świetne narzędzie jak nie potrafisz w swoim języku programowania wyciągnąć całej ścieżki czy nagłówka “Host” z żądania ale uwierz mi, htaccess is not the way to go.
Jedyną rzeczą w htaccessie powinno być jedno przekierowanie w stylu “wszystko co nie jest obrazkiem/css/js wyślij do aplikacji” i tyle, żadnych głupot w stylu:
RewriteRule ^about$ index.php?page=about [NC,L]
Czy jeszcze gorzej,
RewriteRule ^about$ index.php?pageid=1 [NC,L]
Czemu ? Parę powodów:
- NIE WSZYSCY UŻYWAJĄ APACHA, gdy admin chce zainstalować lighttpd czy nginxa żeby odciążyć serwer, musi męczyć się z przerabianem rewritów na inny format + robić to po każdej zmianie w aplikacji (bo programiści zdają sie uczyć składni apachowego mod_rewrite jeszcze zanim nauczą robić SQLi)
- Masz 2 punkty w których musisz coś zmieniać gdy chcesz zmienić routing adresów w aplikacji, zarówno kod aplikacji jak i htaccess
- Nie każdy programista znający dobrze język w którym programujesz będzie znał htaccess, wiec zamiast robić użyteczną pracę będzie się go uczył ;]
A czym zastąpić takie rewrity ? W najprostrzym wypadku wsadzamy wszystko “nie dynamiczne” do katalogu static/
a resztę przekierowujemy na rdzeń aplikacji. Od razu wiadomo co backupować, aplikacja jest oddzielona od swoich danych, rewrity są nieskomplikowane a admin może potem łatwo wydzielić katalog ze statyczną częścią aplikacji.
SQL Injection
Naprawdę, obrona przed SQL injection jest tak łatwa że nikt nie powinien popełniac tego typu błędów, niestety na studiach uczą SQL ale nie uczą bezpiecznego użycia go w aplikacji webowych.
W skrócie zamiast po prostu robić “SELECT * FROM users WHERE user = $jakas_zmienna” (co bardziej zapoznani bawią się w escapowanie danych co jest dobrą praktyką, ale zbędną w tym przypadku, może z wyjątkiem wywalaniem ‘%’ w “LIKE a = $costam”) używamy magicznej rzeczy zwanej “kwerendą z parametrem” (jeżeli dobrze tłumaczę).
Otóż jest to takie magiczne cuś w którym zamiast martwić sie o escapowanie wejścia i pisać:
$data=$db->query("SELECT * FROM users WHERE user = '$user'");
piszemy
$data=$db->query("SELECT * FROM users WHERE user = ?", $user);
Co to daje? Baza danych przypisuje $user do pierwszego znaku ‘?’ i wie że to tyczy się tylko tego jednego porównania, więc ustawienie user na “a OR 1=1” wyszuka nam usera “a OR 1=1” a nie zrzuci całą zawartość tabeli user
Hardcoding
W sensie “hardcodowania użytecznych zmiennych jako stałych”. Może Ci się wydawać że jak piszesz serwis tanczacechomiki.pl
to nazwa strony nigdy się nie zmieni, ale czasami trzeba zrobić dev.tanczacechomiki.pl
, albo dev.server/tanczacechomiki
a wtedy przy dużej ilości hardkodowanych rzeczy (tu gdzieś w kodzie tu coś w bazie itp.) sprowadza sie to do ostrego mailingu miedzy adminem a programistą/ami i stratą czasu.
Tak samo ścieżki, $imgpath="./img";
zamiast $imgpath="/var/www/img";
, dostęp do bazy i wszystko inne co może się zmienić jak np lista newsów na głównej
Sesje w bazie
Tak wiem że to świetny sposób zapewnienia że sesja będzie jednocześnie na wszystkich serwerach aplikacji, ale póki nie masz zrobionego ładnego cachowania (memcached czy coś innego) i update-tylko-gdy-sie-zmienia-i-to-niezbyt-czesto-a-nie-co-refresh-strony sesji to każdy request = 1 select do bazy (która jest współdzielona pomiędzy appserwerami) zamiast 1 select = request na lokalny dysk. A z trzymaniem sesji usera do konkretnego serwera już admin sobie poradzi (np. haproxy).
DO’s
Używaj GITa!
Lub innego systemu kontroli wersji (najlepiej rozproszonego, darmowy backup i szybszy ;] ) do WSZYSTKIEGO (może oprócz obrazków dodanych przez userów). Im lepiej znasz swój VCS (version control system) tym wygodniej, szybiej i efektywniej będziesz go używał. Niech każdy programers podpisuje się swoim userem i emailem (żeby nie było commitów podpisanych kurczaczek@localhost), zrób brancha stable (z tym co aktualnie jest na prod) i tam commituj PRZETESTOWANE zmiany, robienie brancha na każdy większy feature i bug to też bardzo dobry pomysł.
Flow typu branch dev (tu wchodza wszystkie latest and greatest features) -> testing (testy przed wydaniem nowej wersji) -> stable (wersja stabilna czyli to co jest zdeployowane lub będzie zdeployowane) zwykle też dobrze sie sprawdza, w takim układzie:
- bugfixy do stable robione są jako branch wychodzący ze stable i mergowany do stable gdy przejdzie testy i bug
- w testing naprawiane są tylko bugi przed wejściem do stable
- w dev wchodzą nowe ficzery i bugfixy do tych ficzerów
Nawet gdy piszesz coś sam, zrobienie brancha “co jest na produkcji” umożliwia łatwe porównanie zmian (git diff stable)
Umożliwia to łatwe cofanie się w wypadku pojawienia się bugu na produkcji i bugfixy dodane w stable można łatwo wrzucić do reszty, wystarczy “git merge stable”.
Rozdziel pliki statyczne od reszty
Zrób katalog static/
, lub po prostu s/
na wszystko co nie jest dynamiczne, ułatwia to rewrity i ew. rozdzielenie aplikacji na serwer serwujący pliki statyczne i serwer aplikacji. Jeszcze lepiej gdy rozdzielisz je na “static forever” (czyli css/js/obrazki należące do layoutu strony) i “user generated” czyli awatary, obrazki dodane przez userów itp.
Cache i DB master-slave
To już gdy aplikacja ma/jest popularna, rozdzielenie zapytań do bazy na to “do odczytu” i “do zapisu” (tzn. możliwość podania innej bazy do zapisu i do odczytu) znacznie ułatwia skalowania takiego np. MySQLa (tam postawienie mastera + 1 lub więcej slavów jest proste), tak samo dobrze przemyślane cachowanie (chociażby chyba najprostrzy w użyciu memcache) potrafi obciąć load serwera DB parokrotnie (taki query_cache w MySQLu się nie sprawdza przy większym ruchu bo każdy zapis do tabeli czyści cache). Ale to już jak masz naprawdę spory serwis :). Chyba że aplikacja korzysta z którejś z baz NoSQL która ma “wbudowane” skalowanie, wtedy już jest to załatwione od razu, chociaż i wtedy warto przystosować app do ew. dodania cache np. zrobić wrapper na funkcę odpytującą bazę z dodatkowym parametrem określającym ile sek. wynik może być cachowany (w najprostrzym przypadku)
Im mniej zapisów tym lepiej
Odczyty się skalują, zapisy nie, zwłaszcza jeżeli chodzi o bazę, często zrobienie jednego dodatkowego odczytu żeby uniknąć zapisu (tzn “zapisz tylko jeżeli coś się zmieniło”) jest dużo “tańsze”, zwłaszcza przy korzystaniu z cache. Ale o samym cachowaniu możnaby napisać książkę
Jeden skrypt deployujący/budujący aplikację
Tu chyba nie trzeba wyjaśniać. Łatwo wprowadzić nową osobę, łatwo przeprowadzać testy (nowa wersja ? odpal skrypt i idź na kawę). Tylko musi być dobrze skomentowany(szczególnie co niektóre regexpy ;) ) żeby w razie czego można było odtworzyć kroki skryptu.
I to tyle ;]. Może kiedyś powstanie część 2 ;]
Edit: Parę poprawek w “Cache i DB”