Jak wiecie, napisałem autorski CMS, który jest w pewnym sensie frameworkiem, ponieważ poza panelem administracyjnym, dostarcza szkieletu i narzędzi do budowy stron i aplikacji internetowych. Zdradzę więc co i jak zrobiłem. Może komuś się przyda podczas tworzenia własnego. A przynajmniej podbuduje swoje ego śmiejąc się z gościa, który cierpi na „nadmiar wolnego czasu” :)
Nie mam żadnego wykształcenia związanego z programowaniem w jakimkolwiek języku (nie licząc kilku zajęć na Politechnice), w związku z czym mogę czasami pisać bzdury z punktu widzenia zawodowców, znających tajemne akronimy np. MVC, SRP czy KGB.
Jestem samoukiem praktykiem, wiedzę czerpię głównie z internetu. I chociaż nie ogarniam teoretycznego bełkotu prowadzonego na grupach dyskusyjnych, nie przeszkadzało mi to w zbudowaniu własnego CMS-a i kilku nieźle działających bibliotek.
Nie jest to może majstersztyk sztuki programowania, ale system wdrażam z powodzeniem, strony działają bez błędu, a co najważniejsze bardzo szybko.
Być może moja nieznajomość teorii dała mi tę korzyść, że nie miałem żadnych skaz i złych przyzwyczajeń programistycznych i pozwoliła mi spojrzeć na temat z zupełnie innej strony. Mój kod nie jest najpiękniejszy, cały czas coś w nim zmieniam, ulepszam, optymalizuję i rozbudowuję. Staram się pilnować, żeby był krótki i po każdej aktualizacji czyszczę go ze śmieci. Zajmuje to trochę czasu, ale dla mnie najważniejsze jest trzymanie kodu w ryzach i posiadanie nad nim 100% kontroli.
Wielu z Was uważa to za totalną stratę czasu, bo po co robić od nowa coś, co już istnieje i działa? Problem w tym, że funkcje czy klasy udostępnione publicznie są pisane tak, aby były jak najbardziej uniwersalne. Plus tego taki, że czytamy dokumentację i po prostu korzystamy z funkcji nie wnikając, co w niej siedzi. W praktyce często jest to nawet 90% zbędnego kodu.
Jakiś czas temu musiałem znaleźć błąd na jednej stronie, postawionej zresztą na Zendzie. Znalazłem odpowiednią klasę (ponad 25kB), wywaliłem całą masę komentarzy i sampli, potem usunąłem kod wstecznej kompatybilności i zostało mi około 1kB kodu właściwego, gdzie dopiero znalazłem zwykłą literówkę. Inna sprawa, że zamiast całej klasy wystarczyłaby prosta funkcja zawierająca kilkanaście linijek w PHP (mam zresztą taką w swoim systemie).
Osobiście uważam, że pisanie czegoś od nowa pozwala wiele się nauczyć. Mógłbym korzystać z gotowych klas np. do obsługi bazy danych i wciąż nie wiedziałbym jak to działa na najniższym poziomie. A taka wiedza często się przydaje i niejednokrotnie uratowała mi dupę.
Wychowałem się na komputerach 8-bitowych (Atari 65XE), gdzie każdy bajt i cykl procesora był na wagę złota. Stąd moje zamiłowanie do optymalizacji, optymalizacji i jeszcze raz optymalizacji.
Nie było to łatwe. Pierwsza wersja systemu powstała bez wcześniejszego planowania i była w zasadzie zlepkiem funkcji. Potem gruntownie przeprojektowałem całość, co pozwoliło mi zmniejszyć ilość tablic w bazie z 12 do 6 (przy jednoczesnej rozbudowie ich funkcjonalności). Ostatecznie napisałem klasę, która sprawnie te tablice obsługuje.
Ponadto zminimalizowałem ilość folderów i plików. W panelu admina ten sam plik obsługuje między innymi listę wpisów, edycję danego wpisu a także akcje związane z nim. Można mieć zarzut, że niepotrzebnie wszystko siedzi w jednym miejscu, ale dla mnie jest to logiczne. W końcu to wszystko realizuje jedno zadanie. Po co tworzyć dodatkowe pliki i potem się w nich gubić, skoro i tak nie będą użyte nigdzie indziej? Dzięki temu udało mi się sprowadzić cały system do około 60 plików, nie licząc edytora TinyMce czy biblioteki jQuery - są to jedyne komponenty, których nie napisałem sam. W sumie jest jeszcze Adminer, ale on działa w pewnym sensie niezależnie i zastępuje mi phpMyAdmin.
Największym wyzwaniem było zaprojektowanie uniwersalnej tablicy zawierającej wszystkie treści. W pierwszej wersji bloga miałem osobną tablicę na kategorie, osobną na wpisy, osobną na kategorie linków, osobną na linki, pliki itp... Bardzo szybko mi się to wszystko rozrosło. A gdybym chciał dodać galerię czy jakieś inne dane?
Podpatrzyłem więc strukturę bazy WordPressa i wzorując się nieco, utworzyłem coś podobnego. Co prawda nie zrobię na tej tablicy sklepu czy forum, bo rozrośnie się do gigantycznych rozmiarów, ale do większości typowych zastosowań jest idealna. Jedna z kolumn zawiera ID rodzica, dzięki czemu tworzę strukturę drzewiastą, zaś inna zawiera typ danych np. newsy czy pliki.
A jeśli w jakimś serwisie potrzebuje obsługiwać dużą ilość konkretnych danych (np. głosowanie na projekt w Budżecie Obywatelskim), to tworzę dodatkową tablicę i rozszerzam główną klasę systemu.
Zdarza mi się poprawiać cudzy kod (chociaż staram się tego unikać i kasa musi być naprawdę atrakcyjna, żebym się tego podjął) i czasami spotykam się z czymś takim, że plik zawiera tylko jedno polecenie, mianowicie includowanie innego pliku. Do dziś nie rozgryzłem celu takiego działania. Zakładam, że to może być tymczasowa proteza i kiedyś w tym pliku będzie coś innego. Ale mogę się mylić.
Nie wiem, jak takie wielokrotne includowanie fizycznie realizują serwery, ale z ogólnego doświadczenia programistycznego wiem, że operacje na plikach należą do jednych z najwolniejszych. Być może piszę teraz głupoty, ale wydaje mi się, że jeden większy plik (zawierający nawet funkcje aktualnie nieużywane) zostanie przetworzony szybciej niż kilka małych. Stąd najważniejsze operacje i dane zawarłem w kilku plikach realizujących następujące zadania:
Te kilka plików ładowanych jest zawsze w panelu admina i po stronie usera.
Połączyłem też wykonywanie akcji (np. z formularzy) z samymi stronami zawierającymi formularz. Dzięki temu nie mam bałaganu w plikach. Niedawno wprowadziłem tokenizację requestów obsługujących formularze, co dodatkowo zabezpiecza system przez spreparowanymi złośliwymi linkami.
Jak wiadomo najbardziej podatnym miejscem na włamania jest przekazywanie parametrów, zwłaszcza przez GET. Dlatego warto przekazywać ich jak najmniej i najlepiej, żeby to były liczby, bo wystarczy zastosować rzutowanie (int)$_GET["zmienna"] i próby przemycenia niebezpiecznego kodu idą na spacer.
Czytałem kiedyś, że głównym celem zaprojektowania języka C było programowanie przy jak najmniejszej ilości stuknięć w klawiaturę. Każdy chyba przyzna, że autorom się udało. To co np. w Pascalu zajmuje kilka (-naście?) linijek kodu, w C/C++ można zapisać w jednej. Przy czym jest to najszybszy w działaniu (zaraz po assemblerze) język programowania.
Może właśnie z tego powodu składnia C stała się wzorem dla innych języków, w tym także sieciowych takich jak JavaScript czy PHP. W zasadzie przerzucenie się z C na PHP to tylko kwestia poznania nowych funkcji systemowych (ale od czego manual php.net). Przy czym PHP ma dużo więcej ułatwień (np. brak definicji typów zmiennych czy konieczności alokowania pamięci). Jest to niestety bardzo wolny język. Wiem, wiem, serwer potrafi sobie go skompilować i trzymać skompilowaną wersję gdzieś w pamięci podręcznej, ale i tak to nie jest to samo co program skompilowany pod konkretny procesor.
PHP daje dużą swobodę programiście (większą nawet niż C). Początkujący bardzo go lubią, bo jest łatwy do nauczenia ale jednocześnie pozwala na „niechlujne” programowanie, przez co bardzo łatwo o błąd, zwłaszcza gdy w grę wchodzą operatory przypisania i porównania. Także nieznajomość kolejności operatorów czy punktów sekwencji potrafi spowodować dziwne zachowanie programu.
Ja wychodzę podobnego założenia, które przyświecało twórcom języka C/C++: mianowicie programista wie co robi (a przynajmniej powinien) i świadomie korzysta ze wszystkich udostępnionych narzędzi. Pozwoliło to znacząco zmniejszyć ilość kodu i przyspieszyć działanie systemu.
Dlatego ważne są dwie znane (ale jak zauważyłem, rzadko stosowane) reguły: KISS (Keep It Simple Stupid) i DRY (Don't Repeat Yourself).
Widziałem kody źródłowe różnych programów, funkcji i klas i często ręce mi opadały. Cała masa stałych i zmiennych. Proste wyrażenia rozbite na jeszcze prostsze. A objętość komentarzy przekraczała objętość kodu właściwego.
Podobno jedną z podstawowych zasad programowania jest pisanie samokomentującego się kodu, a komentarze nie powinny zajmować więcej niż 10-20% całości. Moim zdaniem to bardzo słuszna droga. Ja tu czasami przesadzam w drugą stronę, bo u mnie komentarze zajmują może 5%. Ale póki programuję sam, to nie mam z tym problemu, a gdy będę wypuszczał CMS w świat, to przygotuję kompletną dokumentację.
Osobiście uważam, że przekomentowany kod jest zbyt nieczytelny. Lubię mieć na ekranie jak najwięcej „mięsa”, żeby bez konieczności przewijania ogarnąć całą metodę. Ewentualny wydruk też wolę mieć na jednej kartce niż dziesięciu. Skoro język daje taką możliwość (a PHP daje), to uważam, że powinno się pisać jak najzwięźlej. Niedawno spotkałem się z następującym wyrażeniem:
if ($warunek == 1)
{
$wynik = "wartość 1";
}
if ($warunek !=1)
{
$wynik = "wartość 2";
}
Dlaczego tego nie zapisać tak:
$wynik=($warunek==1)? "wartość 1" : "wartość 2";
Jeśli dla kogoś drugi zapis jest nieczytelny, to nie powinien zajmować się programowaniem.
Słyszałem kiedyś teorię, że programista będzie w stanie zająć całą dostępną pamięć. Niestety często jest to prawda. O ile z aplikacją desktopową problem będzie miał tylko użytkownik danego programu o tyle zasobami serwera musimy dzielić się z wieloma innymi użytkownikami. Dlatego warto trzymać się zasady: jeśli coś się da zrobić prosto, to tak właśnie róbmy. Nie komplikujmy na siłę programu, w dużym o wiele łatwiej o pomyłkę (nawet literówkę). Nie mówiąc już o rozbudowie. Więc im mniej miejsc potencjalnego błędu tym lepiej.
Zasada DRY jest w zasadzie uzupełnieniem KISS i w sumie też powinna być znana przez każdego kodera. Niestety z wielu względów nie jest stosowana. Pętla foreach jest moim najlepszym przyjacielem, zwłaszcza po odczytaniu tablicy z bazy danych. A jeśli mam dwa podobne fragmenty kodu to prawie zawsze wrzucam je w jedną funkcje lub metodę. I właśnie o to głównie chodzi w DRY.
W miarę jak rozbudowuję swój system, coraz częściej miewam sytuacje, że niezależne moduły mają podobne fragmenty (które zresztą często były pisane na szybko i spokojnie można było użyć krótszych konstrukcji). Wrzucam więc je do funkcji (a czasami tworzę klasę) i z miejsca zyskuję kilka kB i łatwość pisania kolejnych modułów, gdzie zamiast kopiować duży fragment kodu, kopiuję tylko wywołanie funkcji z odpowiednimi parametrami.
Miałem taką sytuację z wysyłaniem e-maila z formularza kontaktowego. Po zrobieniu kilku serwisów doszedłem do wniosku, że przecież mogę użyć jednej funkcji SendMail a całą resztę (formatowanie, walidację i wysyłanie) realizuję już w środku.
Oczywiście nie należy przesadzać, bo może dojść do tego, że funkcja, w zależności od przekazanych parametrów będzie robić całkiem różne rzeczy, a potem okaże się, że rozbudowa takiej funkcji to dużo więcej zabawy niż to ma sens, trzeba ją rozbijać na podfunkcje a te znowu... We wszystkim trzeba zachować umiar i zdrowy rozsądek i zrobić z takiej funkcji klasę.
Optymalizacja, optymalizacja i jeszcze raz optymalizacja. Staram się pisać jak najzwięźlej, nie tworzę skomplikowanych struktur (kodu, jak i danych - o budowie tablic już pisałem i pewnie jeszcze wspomnę). Nie stosuję wielokrotnego dziedziczenia, abstrakcji, prototypów i innych rzeczy, które można zrealizować w prostszy sposób, z korzyścią dla objętości kodu i szybkości działania.
Podczas rozbudowy ciągle znajduję miejsca, które można skrócić i uprościć. Czasami przepisuję od nowa całe kawałki kodu, bo okazuje się, że w międzyczasie wpadłem na pomysł całkiem nowego podejścia do problemu. Daje mi to naprawdę spora frajdę.
Pisanie CMS-ów jest dla ludzi mających czas, zapał i motywację. Moją motywacją jest coraz szybsze tworzenie serwisów internetowych, a co za tym idzie - coraz lepszy stosunek zarobków do czasu pracy.