Jak pisać czysty kod? Dobre praktyki kodowania na podstawie PHP.
Zasady pisania czystego kodu są często pomijane w książkach, kursach i tutorialach. Uważam, że to poważny błąd. Stosowanie się do powszechnie znanych dobrych praktyk kodowania może ułatwić pracę zarówno nam, jak i innym – jeśli jako programiści będziemy się do tych zasad stosować, ułatwimy sobie nawzajem pracę. Na wstępie chciałbym też zaznaczyć, że jestem programistą PHP, więc przykłady będą opierać się właśnie na tym języku, ale większość z poniższych zasad można stosować również w innych językach.
Zasady ogólne
Na początku chciałbym przedstawić kilka ogólnych zasad, których warto używać przy projektowaniu rozwiązań, aby łatwiej zrozumieć, po co w ogóle stosować się do jakichkolwiek konwencji. Nie wolno zakładać, że nie będzie trzeba już nigdy pracować z aktualnie pisanym kodem – to częsty błąd popełniany przez programistów. Często zdarza się, że dostajemy zadania, od których chcemy jak najszybciej uciec, pozbyć się problemu. Zapominamy, że prawdopodobieństwo, że kod ten trzeba będzie zmienić, ponieważ zmienią się wymagania, lub poprawić, ponieważ znajdzie się tam błąd, jest bardzo duże. Lepiej więc dla nas, jak i dla naszego klienta, jeżeli więcej czasu i energii poświęcimy przy pierwszej implementacji, stosując dobre praktyki, aby kod był „czysty”, zamiast poświęcać go często kilka razy więcej przy kolejnym podejściu do rozwiązania.
Pisz kod tak, aby zrozumieli go inni programiści
Każdy programista potrafi napisać kod zrozumiały dla maszyny, ale tylko dobry programista pisze kod zrozumiały dla człowieka. To zasada powszechnie znana i bardzo dobrze rozumiana przez programistów pracujących w zespołach. Pewnie każdy z programistów spotkał się z sytuacją, gdy musiał zagłębiać się w tajniki kodu „spaghetti” innego programisty lub nawet swój własny.
Dbanie o to, aby kod był przyjemny, potrafi zaoszczędzić czas oraz nerwy – szczególnie w awaryjnych sytuacjach.
Stosuj się do zasad przyjętych w Twoim zespole
Od powyższych zasad istnieje jeden wyjątek: to, że został opracowany jakiś zestaw dobrych praktyk, nie oznacza, że sprawdzą się one w każdym projekcie. Jeżeli Twój zespół postanowił z jakieś zasady zrezygnować, oznacza, że miał jakiś powód. Nie warto iść z tego powodu na wojnę z ludźmi, z którymi powinniśmy przecież współpracować. Jeżeli uważasz, że jakieś zasady przyjęte w projekcie powinny zostać zmienione, omów to z całym zespołem, ponieważ robienie „kreciej roboty” nie opłaci się nikomu.
Zmienne
Używaj zrozumiałych nazw zmiennych. Nazwa zmiennej nie powinna wymagać żadnego komentarza – należy nazywać je tak, aby same nazwy wskazywały na to, co zawierają.
Źle:
$variable = ‘Kamil Marynowski’;
Dobrze:
$fullname = ‘Kamil Marynowski’;
Używaj tego samego słownictwa dla takich samych typów danych – nie ma sensu tworzyć nadmiarowego kodu.
Źle:
setClientName();
setClientAddress();
setClientAge();
Dobrze:
setClient();
Używaj stałych
Stałe mogą nam często pomóc uprościć zrozumienie danego mechanizmu. Zamiast domyślać się, co może oznaczać wpisana na twardo liczba, możemy oznaczyć ją stałą, która sama w sobie będzie zawierać tę informację.
Źle:
switch($typeId) {
case 1:
…
break;
case 2:
…
break;
case 3:
…
break;
default:
…
break;
}
Dobrze:
switch($typeId) {
case Document::PDF:
…
break;
case Document::CSV:
…
break;
case Document::TXT:
…
break;
default:
…
break;
}
Nie dodawaj niepotrzebnego kontekstu
Jeżeli dana zmienna znajduje się w jakiejś strukturze, która dobrze określa jej funkcję, nie powtarzaj w jej nazwie kontekstu tej struktury.
Źle:
class User
{
private $userUsername
private $userFirstname;
private $userLastname;
}
Dobrze:
class User
{
private $username;
private $firstname;
private $lastname;
}
Porównania
Ponieważ PHP używa dynamicznego typowania zmiennych, dobrą praktyką jest używanie porównywania, które sprawdza również typ danych.
Źle:
$a = ‘11’;
$b = 11;
if ($a == $b) {
foo();
/*
Wyrażenie spełnia warunek, choć zmienna $a typu string jest czymś innym niż zmienna $b typu integer.
*/
}
Dobrze:
$a = ‘11’;
$b = 11;
If ($a === $b) {
foo();
/*
Wyrażenie nie spełnia warunku, ponieważ obie zmienne różnią się typem danej.
*/
}
Funkcje
- Używaj domyślnych wartości dla argumentów
- Ogranicz liczbę argumentów funkcji
Aby zachować czytelność deklaracji funkcji, powinniśmy ograniczyć liczbę argumentów funkcji do maksymalnie 3. Aby ograniczyć liczbę parametrów funkcji, możemy wszystkie potrzebne zmienne umieścić w tzw. „payload”.
Źle:
function setClientData(string $street, string $city, string $postalCode): array
Dobrze:
function setClientData(array $addressData)
Nazwa funkcji powinna mówić, co dana funkcja robi
Tak, jak w przypadku zmiennych, każda funkcja powinna posiadać nazwę, która jasno i wyraźnie określa to, za co dana funkcja odpowiada.
Źle:
class User
{
//…
public function handle($var)
{
$this->age = $var;
}
}
Dobrze:
class User
{
//…
public function setAge(int $age)
{
$this->age = $age;
}
}
Nie używaj flag jako argumentów funkcji
Jeżeli funkcja posiada flagę jako parametr, oznacza to, że wykonuje więcej niż jedną rzecz. Każda funkcja powinna wykonywać tylko jedną rzecz na raz.
Źle:
function doMathOperation(int $number, int $number2, bool $addition)
{
if ($addition == true) {
return $number + $number2;
} else {
return $number – $number2;
}
}
Dobrze:
if ($addition) {
add($number, $number2);
} else {
substract($number, $number2);
}
- Unikaj instrukcji warunkowych
Źle:
class Car
{
//…
public function getColor(): string
{
switch($this->model) {
case ‘Audi’:
return ‘black’;
break;
case: ‘Bmw’:
return ‘green’;
break;
}
}
}
Dobrze:
interface Car
{
public function getColor(): string;
}
class Audi implements Car
{
public function getColor(): string
{
return ‘black’;
}
}
class Bmw implements Car
{
public function getColor(): string
{
return ‘green;
}
}
Utrzymuj odpowiedni porządek w klasach
Uważam, że obranie jednego porządku właściwości oraz metod wraz z modyfikatorami dostępu jest bardzo istotne. Sam obrałem jeden z nich i zawsze się go trzymam, dzięki czemu bez problemu jestem w stanie odnaleźć się w każdej z klas, które tworzę. Nie jest jednak najważniejsze, aby obrać konkrety, ale aby się ich trzymać. Oto przykład porządku, który sam stosuje.
class Example extends Extendedclass implements ImplementedInterface
{
const CONSTANTS = ‘const’;
use exampleTrait;
public static $property;
protected static $property;
private static $property;
public static function method() {};
protected static function method();
private static function method();
public $property;
protected $property;
private $property;
public function __construct() {}
public function __desctruct() {}
public function __get() {}
public function __get() {}
public function getters () {}
public function setters() {}
public function method() {}
final public function method() {}
protected function method() {}
final protected function() method() {}
private function method() {}
abstract public function methods() {}
abstract protected function methods() {}
abstract private function methods() {}
}
SOLID
Solid to zbiór praktyk architektonicznych w programowaniu obiektowym, które ułatwiają tworzenie kodu zrozumiałego, bardziej otwartego na zmiany i łatwiejszego w zarządzaniu. W słowie SOLID zawarte jest 5 najważniejszych zasad, którymi każdy programista powinien się kierować. Nie da się ich stosować na raz w 100%, ale są to wskazówki, które sprawią, że kod będzie lepszej jakości.
- S jak Single Responsibility Principle.
Zasada pojedynczej odpowiedzialności – mówi o tym, że każda funkcja, metoda czy klasa powinna wykonywać jedną konkretną rzecz. - O jak Open Close Principle
Mówi o tym, że klasy powinny być zamknięte na modyfikacje, ale otwarte na rozszerzenia. Powinniśmy tak projektować nasze rozwiązania, aby przy zmianach kod rozszerzać, a nie modyfikować już istniejący, bo modyfikacja może prowadzić do błędów, gdy nasz kod pracuje już w środowisku produkcyjnym. - L jak Liskov Substitution Principle
W dużym skrócie: zasada ta oznacza, że klasy dziedziczące po klasie bazowej nie powinny wpływać na działanie klasy, którą rozszerzają. - I jak Interface Segregation Principle
Zasada segregacji interfejsów, która lepiej mieć wiele mniejszych interfejsów niż jeden zbyt duży i rozbudowany. Każdy interfejs powinien zawierać minimalną liczbę niezbędnych metod, a każda metoda, która nie jest związana z konkretnym interfejsem, powinna się znaleźć w innym. - D jak Dependency Inversion Principle
Zasada odwracania zależności mówi, że moduły wysokiego poziomu nie powinny zależeć od modułów niskiego poziomu. Nie powinniśmy operować bezpośrednio na instancjach naszych klas czy metod, a na ich interfejsach.
DRY = Don’t repeat yourself
Kolejna ważna zasada mówi o tym, aby nie powielać własnego kodu. Powinniśmy tak projektować rozwiązania, aby kod, który piszemy, nigdzie się nie powtórzył. Jeżeli istnieją takie funkcjonalności, które potrzebują zastosowania takiego samego mechanizmu, powinniśmy wyabstrahować taki kod, np. do serwisu, aby zamknąć go w metodzie, a potem użyć jej, skracając tym samym długość kodu, jak i zwiększając jego czytelność.
KISS = Keep it simple, stupid
Nieważne, jak bardzo skomplikowanymi strukturami potrafisz operować – Twój kod powinien operować na tak prostych i przejrzystych strukturach, jak to tylko możliwe (oczywiście tak, aby zachować swoją pierwotną funkcjonalność i nie złamać przy tym zasad opisanych powyżej). Nie potrzeba w naszym kodzie, aby programista, który być może kiedyś będzie musiał z nim pracować, musiał przechodzić gehennę brnięcia po naszych logicznych labiryntach. Kod powinien być jasny i prosty, aby czytało się go jak dobrą książkę.
Mam nadzieję, że jasno i zrozumiale wytłumaczyłem, na czym polegają poszczególne zasady. Uważam, że jest to temat bardzo istotny i każdy dobry programista powinien być nim zainteresowany. Pamiętajmy, że dbając o dobrą jakość kodu, pomagamy nie tylko innym programistom, ale przede wszystkim sobie.