Skip to content

ranggakd/clean-code-php

ย 
ย 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Kode Bersih PHP ๐Ÿ› (Clean Code PHP) ๐Ÿงผ

Daftar Isi

  1. Pengenalan
  2. Variabel
  3. Perbandingan
  4. Fungsi
  5. Objek dan Struktur Data
  6. Kelas
  7. SOLID
  8. Donโ€™t Repeat Yourself (DRY)
  9. Terjemahan

๐Ÿ” kembali ke atas

Pengenalan

Prinsip rekayasa perangkat lunak dari buku Clean Code milik Robert C. Martin diadaptasi untuk PHP. Ini bukanlah pedoman gaya. Ini merupakan pedoman untuk memproduksi perangkat lunak yang mudah dibaca, mudah digunakan kembali dan dapat direfaktor dalam PHP.

Tak semua prinsip disini harus diikuti secara ketat, dan bahkan lebih sedikit lagi yang disepakati secara universal. Ini adalah pedoman dan tidak lebih, namun itu dikodifikasikan selama bertahun-tahun dari pengalaman kolektif oleh penulis Clean Code.

Terinspirasi dari clean-code-javascript.

Meskipun banyak pengembang masih menggunakan PHP 5, sebagian besar contoh di artikel hanya bekerja dengan PHP 7.1+.

๐Ÿ” kembali ke atas

Variabel

Gunakan nama variabel yang bermakna dan dapat diucapkan

Buruk:

$ymdstr = $moment->format('y-m-d');

Baik:

$currentDate = $moment->format('y-m-d');
Pemfaktoran ulang:
- $ymdstr = $moment->format('y-m-d');
+ $currentDate = $moment->format('y-m-d');

๐Ÿ” kembali ke atas

Gunakan kosakata yang sama untuk jenis variabel yang sama

Buruk:

getUserInfo();
getUserData();
getUserRecord();
getUserProfile();

Baik:

getUser();
Pemfaktoran ulang:
- getUserInfo();
- getUserData();
- getUserRecord();
- getUserProfile();
+ getUser();

๐Ÿ” kembali ke atas

Gunakan nama yang dapat dicari (bagian 1)

Kita akan membaca lebih banyak kode daripada yang pernah kita tulis. Penting bahwa kode yang kita tulis dapat dibaca dan dicari. Dengan tidak menamai variabel bermakna untuk pemahaman program kita, itu merugikan pembaca kita. Buat nama yang dapat dicari.

Buruk:

// Untuk apa 448 itu?
$result = $serializer->serialize($data, 448);

Baik:

$json = $serializer->serialize($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
Pemfaktoran ulang:
- $result = $serializer->serialize($data, 448);
+ $json = $serializer->serialize($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);

๐Ÿ” kembali ke atas

Gunakan nama yang dapat dicari (bagian 2)

Buruk:

class User
{
    // Untuk apa 7 itu?
    public $access = 7;
}

// Untuk apa 4 itu?
if ($user->access & 4) {
    // ...
}

// Apa yang terjadi di sini?
$user->access ^= 2;

Baik:

class User
{
    public const ACCESS_READ = 1;

    public const ACCESS_CREATE = 2;

    public const ACCESS_UPDATE = 4;

    public const ACCESS_DELETE = 8;

    // Pengguna sebagai default dapat membaca, membuat dan memperbarui sesuatu
    public $access = self::ACCESS_READ | self::ACCESS_CREATE | self::ACCESS_UPDATE;
}

if ($user->access & User::ACCESS_UPDATE) {
    // lakukan edit
}

// Tolak hak akses untuk membuat sesuatu
$user->access ^= User::ACCESS_CREATE;
Pemfaktoran ulang:
class User
{
-    public $access = 7;
+    public const ACCESS_READ = 1;
+
+    public const ACCESS_CREATE = 2;
+
+    public const ACCESS_UPDATE = 4;
+
+    public const ACCESS_DELETE = 8;
+
+    public $access = self::ACCESS_READ | self::ACCESS_CREATE | self::ACCESS_UPDATE;
}

- if ($user->access & 4) {
+ if ($user->access & User::ACCESS_UPDATE) {
}

- $user->access ^= 2;
+ $user->access ^= User::ACCESS_CREATE;

๐Ÿ” kembali ke atas

Gunakan variabel penjelas

Buruk:

$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{5})$/';
preg_match($cityZipCodeRegex, $address, $matches);

saveCityZipCode($matches[1], $matches[2]);

Tidak buruk:

Ini lebih baik, tapi kita masih sangat bergantung pada regex.

$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{5})$/';
preg_match($cityZipCodeRegex, $address, $matches);

[, $city, $zipCode] = $matches;
saveCityZipCode($city, $zipCode);

Baik:

Kurangi ketergantungan pada regex dengan menamai subpola.

$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,]+,\s*(?<city>.+?)\s*(?<zipCode>\d{5})$/';
preg_match($cityZipCodeRegex, $address, $matches);

saveCityZipCode($matches['city'], $matches['zipCode']);
Pemfaktoran ulang:
$address = 'One Infinite Loop, Cupertino 95014';
- $cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{5})$/';
+ $cityZipCodeRegex = '/^[^,]+,\s*(?<city>.+?)\s*(?<zipCode>\d{5})$/';
preg_match($cityZipCodeRegex, $address, $matches);

- saveCityZipCode($matches[1], $matches[2]);
- [, $city, $zipCode] = $matches;
- saveCityZipCode($city, $zipCode);
+ saveCityZipCode($matches['city'], $matches['zipCode']);

๐Ÿ” kembali ke atas

Hindari nesting terlalu dalam dan return lebih awal (bagian 1)

Terlalu banyak pernyataan if-else dapat membuat kode kita sulit diikuti. Eksplisit lebih baik daripada implisit.

Buruk:

function isShopOpen($day): bool
{
    if ($day) {
        if (is_string($day)) {
            $day = strtolower($day);
            if ($day === 'friday') {
                return true;
            } elseif ($day === 'saturday') {
                return true;
            } elseif ($day === 'sunday') {
                return true;
            }
            return false;
        }
        return false;
    }
    return false;
}

Baik:

function isShopOpen(string $day): bool
{
    if (empty($day)) {
        return false;
    }

    $openingDays = ['friday', 'saturday', 'sunday'];

    return in_array(strtolower($day), $openingDays, true);
}
Pemfaktoran ulang:
- function isShopOpen($day): bool
+ function isShopOpen(string $day): bool
{
-    if ($day) {
-        if (is_string($day)) {
-            $day = strtolower($day);
-            if ($day === 'friday') {
-                return true;
-            } elseif ($day === 'saturday') {
-                return true;
-            } elseif ($day === 'sunday') {
-                return true;
-            }
-            return false;
-        }
-        return false;
-    }
-    return false;    
+    if (empty($day)) {
+        return false;
+    }
+
+    $openingDays = ['friday', 'saturday', 'sunday'];
+
+    return in_array(strtolower($day), $openingDays, true);
}

๐Ÿ” kembali ke atas

Hindari nesting terlalu dalam dan return lebih awal (bagian 2)

Buruk:

function fibonacci(int $n)
{
    if ($n < 50) {
        if ($n !== 0) {
            if ($n !== 1) {
                return fibonacci($n - 1) + fibonacci($n - 2);
            }
            return 1;
        }
        return 0;
    }
    return 'Not supported';
}

Baik:

function fibonacci(int $n): int
{
    if ($n === 0 || $n === 1) {
        return $n;
    }

    if ($n >= 50) {
        throw new Exception('Not supported');
    }

    return fibonacci($n - 1) + fibonacci($n - 2);
}
Pemfaktoran ulang:
- function fibonacci(int $n)
+ function fibonacci(int $n): int
{
-    if ($n < 50) {
-        if ($n !== 0) {
-            if ($n !== 1) {
-                return fibonacci($n - 1) + fibonacci($n - 2);
-            }
-            return 1;
-        }
-        return 0;
-    }
-    return 'Not supported';
+    if ($n === 0 || $n === 1) {
+        return $n;
+    }
+
+    if ($n >= 50) {
+        throw new Exception('Not supported');
+    }
+
+    return fibonacci($n - 1) + fibonacci($n - 2);
}

๐Ÿ” kembali ke atas

Hindari Mental Mapping

Jangan paksa pembaca kode kita untuk mengartikan apa maksud variabel. Eksplisit lebih baik daripada implisit.

Buruk:

$l = ['Austin', 'New York', 'San Francisco'];

for ($i = 0; $i < count($l); $i++) {
    $li = $l[$i];
    doStuff();
    doSomeOtherStuff();
    // ...
    // ...
    // ...
    // Tunggu, untuk apa `$li` tadi?
    dispatch($li);
}

Baik:

$locations = ['Austin', 'New York', 'San Francisco'];

foreach ($locations as $location) {
    doStuff();
    doSomeOtherStuff();
    // ...
    // ...
    // ...
    dispatch($location);
}
Pemfaktoran ulang:
- $l = ['Austin', 'New York', 'San Francisco'];
+ $locations = ['Austin', 'New York', 'San Francisco'];

- for ($i = 0; $i < count($l); $i++) {
+ foreach ($locations as $location) {
-    $li = $l[$i];
    doStuff();
    doSomeOtherStuff();
    // ...
    // ...
    // ...
-    dispatch($li);
+    dispatch($location);
}

๐Ÿ” kembali ke atas

Jangan tambahkan konteks yang tak dibutuhkan

Jika nama kelas/objek memberi tahu diri kita sesuatu, jangan ulangi itu pada nama variabel kita.

Buruk:

class Car
{
    public $carMake;

    public $carModel;

    public $carColor;

    //...
}

Baik:

class Car
{
    public $make;

    public $model;

    public $color;

    //...
}
Pemfaktoran ulang:
class Car
{
-    public $carMake;
+    public $make;

-    public $carModel;
+    public $model;

-    public $carColor;
+    public $color;

    //...
}

๐Ÿ” kembali ke atas

Perbandingan

Tidak baik:

Perbandingan sederhana akan mengonversi string menjadi integer.

$a = '42';
$b = 42;

if ($a != $b) {
    // Ekspresi akan selalu dilewati
}

Perbandingan $a != $b mengembalikan FALSE namun faktanya itu TRUE! String '42' berbeda dengan integer 42.

Baik:

Perbandingan identik akan membandingkan tipe dan nilai.

$a = '42';
$b = 42;

if ($a !== $b) {
    // Ekspresi ini terverifikasi
}

Perbandingan $a !== $b mengembalikan TRUE.

Pemfaktoran ulang:
$a = '42';
$b = 42;

- if ($a != $b) {
+ if ($a !== $b) {
    //
}

๐Ÿ” kembali ke atas

Operator null coalescing

Null coalescing merupakan operator baru yang diperkenalkan di PHP 7. Operator null coalescing ?? telah ditambahkan sebagai sintaks pemanis untuk kasus umum keperluan menggunakan ternary dalam hubungannya dengan isset(). Itu mengembalikan operan pertamanya jika ada dan bukan null; jika tidak, itu mengembalikan operan keduanya.

Buruk:

if (isset($_GET['name'])) {
    $name = $_GET['name'];
} elseif (isset($_POST['name'])) {
    $name = $_POST['name'];
} else {
    $name = 'nobody';
}

Baik:

$name = $_GET['name'] ?? $_POST['name'] ?? 'nobody';
Pemfaktoran ulang:
- if (isset($_GET['name'])) {
-    $name = $_GET['name'];
- } elseif (isset($_POST['name'])) {
-    $name = $_POST['name'];
- } else {
-    $name = 'nobody';
-}
+ $name = $_GET['name'] ?? $_POST['name'] ?? 'nobody';

๐Ÿ” kembali ke atas

Fungsi

Gunakan argumen default alih-alih short-circuiting atau persyaratan

Mengenai short-circuiting:

Semantik beberapa operator Boolean dalam beberapa bahasa pemrograman di mana argumen kedua dieksekusi atau dievaluasi hanya jika argumen pertama tidak cukup untuk menentukan nilai ekspresi: ketika argumen pertama dari fungsi AND bernilai false, nilai keseluruhan harus salah; dan ketika argumen pertama dari fungsi OR bernilai true, nilai keseluruhan harus benar.

Tidak baik:

Ini tidak baik karena $breweryName bisa NULL.

function createMicrobrewery($breweryName = 'Hipster Brew Co.'): void
{
    // ...
}

Tidak buruk:

Pendapat ini lebih bisa dimaklumi daripada versi sebelumnya, namun lebih mengontrol nilai variabel.

function createMicrobrewery($name = null): void
{
    $breweryName = $name ?: 'Hipster Brew Co.';
    // ...
}

Baik:

Kita dapat menggunakan type hinting dan yakin bahwa $breweryName tidak akan NULL.

function createMicrobrewery(string $breweryName = 'Hipster Brew Co.'): void
{
    // ...
}
Pemfaktoran ulang:
- function createMicrobrewery($breweryName = 'Hipster Brew Co.'): void
- function createMicrobrewery($name = null): void
+ function createMicrobrewery(string $breweryName = 'Hipster Brew Co.'): void
{
-    $breweryName = $name ?: 'Hipster Brew Co.';
    // ...
}

๐Ÿ” kembali ke atas

Argumen fungsi (idealnya 2 atau kurang)

Membatasi jumlah parameter fungsi sungguh penting karena itu membuat pengujian fungsi kita lebih mudah. Memiliki lebih dari tiga mengarah ke ledakan kombinatorial dimana kita harus menguji banyak kasus berbeda dengan setiap argumen terpisah.

Kasus idealnya tanpa argumen. Satu atau dua argumen boleh saja, dan tiga argumen harus dihindari. Lebih dari itu harus dikonsolidasikan. Biasanya, jika kita memiliki lebih dari dua argumen, maka fungsi kita mencoba melakukan terlalu banyak. Dalam kasus dimana tidak, sebagian besar waktu suatu objek tingkat yang lebih tinggi akan cukup sebagai argumen.

Buruk:

class Questionnaire
{
    public function __construct(
        string $firstname,
        string $lastname,
        string $patronymic,
        string $region,
        string $district,
        string $city,
        string $phone,
        string $email
    ) {
        // ...
    }
}

Baik:

class Name
{
    private $firstname;

    private $lastname;

    private $patronymic;

    public function __construct(string $firstname, string $lastname, string $patronymic)
    {
        $this->firstname = $firstname;
        $this->lastname = $lastname;
        $this->patronymic = $patronymic;
    }

    // getters ...
}

class City
{
    private $region;

    private $district;

    private $city;

    public function __construct(string $region, string $district, string $city)
    {
        $this->region = $region;
        $this->district = $district;
        $this->city = $city;
    }

    // getters ...
}

class Contact
{
    private $phone;

    private $email;

    public function __construct(string $phone, string $email)
    {
        $this->phone = $phone;
        $this->email = $email;
    }

    // getters ...
}

class Questionnaire
{
    public function __construct(Name $name, City $city, Contact $contact)
    {
        // ...
    }
}
Pemfaktoran ulang:
+class Name
+{
+    private $firstname;
+
+    private $lastname;
+
+    private $patronymic;
+
+    public function __construct(string $firstname, string $lastname, string $patronymic)
+    {
+        $this->firstname = $firstname;
+        $this->lastname = $lastname;
+        $this->patronymic = $patronymic;
+    }
+
+    // getters ...
+}
+
+class City
+{
+    private $region;
+
+    private $district;
+
+    private $city;
+
+    public function __construct(string $region, string $district, string $city)
+    {
+        $this->region = $region;
+        $this->district = $district;
+        $this->city = $city;
+    }
+
+    // getters ...
+}
+
+class Contact
+{
+    private $phone;
+
+    private $email;
+
+    public function __construct(string $phone, string $email)
+    {
+        $this->phone = $phone;
+        $this->email = $email;
+    }
+
+    // getters ...
+}
+
class Questionnaire
{
-    public function __construct(
-        string $firstname,
-        string $lastname,
-        string $patronymic,
-        string $region,
-        string $district,
-        string $city,
-        string $phone,
-        string $email
-    ) {
+    public function __construct(Name $name, City $city, Contact $contact)
+    {
        // ...
    }
}

๐Ÿ” kembali ke atas

Nama fungsi seharusnya mengatakan apa yang mereka lakukan

Buruk:

class Email
{
    //...

    public function handle(): void
    {
        mail($this->to, $this->subject, $this->body);
    }
}

$message = new Email(...);
// Apa ini? Sebuah handle untuk sebuah pesan? Apakah kita menulis ke suatu file sekarang?
$message->handle();

Baik:

class Email
{
    //...

    public function send(): void
    {
        mail($this->to, $this->subject, $this->body);
    }
}

$message = new Email(...);
// Jelas
$message->send();
Pemfaktoran ulang:
class Email
{
    //...

-    public function handle(): void
+    public function send(): void
    {
        mail($this->to, $this->subject, $this->body);
    }
}

$message = new Email(...);
//
- $message->handle();
+ $message->send();

๐Ÿ” kembali ke atas

Fungsi seharusnya hanya satu tingkat abstraksi

Ketika kita memiliki lebih dari satu tingkat abstraksi, fungsi kita biasanya melakukan banyak hal. Memisahkan fungsi mengarahkan pada penggunaan kembali (reusability) dan pengujian yang lebih mudah.

Buruk:

function parseBetterPHPAlternative(string $code): void
{
    $regexes = [
        // ...
    ];

    $statements = explode(' ', $code);
    $tokens = [];
    foreach ($regexes as $regex) {
        foreach ($statements as $statement) {
            // ...
        }
    }

    $ast = [];
    foreach ($tokens as $token) {
        // lex...
    }

    foreach ($ast as $node) {
        // parse...
    }
}

Buruk juga:

Kita telah melakukan beberapa fungsionalitas, namun fungsi parseBetterPHPAlternative() masih sangat kompleks dan tidak dapat diuji.

function tokenize(string $code): array
{
    $regexes = [
        // ...
    ];

    $statements = explode(' ', $code);
    $tokens = [];
    foreach ($regexes as $regex) {
        foreach ($statements as $statement) {
            $tokens[] = /* ... */;
        }
    }

    return $tokens;
}

function lexer(array $tokens): array
{
    $ast = [];
    foreach ($tokens as $token) {
        $ast[] = /* ... */;
    }

    return $ast;
}

function parseBetterPHPAlternative(string $code): void
{
    $tokens = tokenize($code);
    $ast = lexer($tokens);
    foreach ($ast as $node) {
        // parse...
    }
}

Baik:

Solusi terbaik adalah memindahkan dependensi fungsi parseBetterPHPAlternative().

class Tokenizer
{
    public function tokenize(string $code): array
    {
        $regexes = [
            // ...
        ];

        $statements = explode(' ', $code);
        $tokens = [];
        foreach ($regexes as $regex) {
            foreach ($statements as $statement) {
                $tokens[] = /* ... */;
            }
        }

        return $tokens;
    }
}

class Lexer
{
    public function lexify(array $tokens): array
    {
        $ast = [];
        foreach ($tokens as $token) {
            $ast[] = /* ... */;
        }

        return $ast;
    }
}

class BetterPHPAlternative
{
    private $tokenizer;
    private $lexer;

    public function __construct(Tokenizer $tokenizer, Lexer $lexer)
    {
        $this->tokenizer = $tokenizer;
        $this->lexer = $lexer;
    }

    public function parse(string $code): void
    {
        $tokens = $this->tokenizer->tokenize($code);
        $ast = $this->lexer->lexify($tokens);
        foreach ($ast as $node) {
            // parse...
        }
    }
}
Pemfaktoran ulang:
+ class Tokenizer
+ {
- function tokenize(string $code): array
+    public function tokenize(string $code): array
+    {
        $regexes = [
            // ...
        ];

        $statements = explode(' ', $code);
        $tokens = [];
        foreach ($regexes as $regex) {
            foreach ($statements as $statement) {
+                $tokens[] = /* ... */;
            }
        }

+        return $tokens;
+    }
+ }

+ class Lexer
+ {
- function lexer(array $tokens): array
+    public function lexify(array $tokens): array
+    {
        $ast = [];
        foreach ($tokens as $token) {
+            $ast[] = /* ... */;
        }

+        return $ast;
+    }
+ }

- function parseBetterPHPAlternative(string $code): void
+ class BetterPHPAlternative
+ {
+    private $tokenizer;
+    private $lexer;
+
+    public function __construct(Tokenizer $tokenizer, Lexer $lexer)
+    {
+        $this->tokenizer = $tokenizer;
+        $this->lexer = $lexer;
+    }
+
+    public function parse(string $code): void
+    {
-    $tokens = tokenize($code);
-    $ast = lexer($tokens);
+        $tokens = $this->tokenizer->tokenize($code);
+        $ast = $this->lexer->lexify($tokens);
        foreach ($ast as $node) {
            // parse...
        }
+    }
+ }

๐Ÿ” kembali ke atas

Jangan gunakan flags sebagai parameter fungsi

Flags memberitahukan pengguna bahwa fungsi melakukan lebih dari satu hal. Fungsi seharusnya melakukan satu hal. Pisahkan fungsi kita jika mereka mengikuti jalur kode berbeda berdasarkan pada suatu boolean.

Buruk:

function createFile(string $name, bool $temp = false): void
{
    if ($temp) {
        touch('./temp/' . $name);
    } else {
        touch($name);
    }
}

Baik:

function createFile(string $name): void
{
    touch($name);
}

function createTempFile(string $name): void
{
    touch('./temp/' . $name);
}
Pemfaktoran ulang:
- function createFile(string $name, bool $temp = false): void
- {
-    if ($temp) {
-        touch('./temp/' . $name);
-    } else {
-        touch($name);
-    }
-}
+ function createFile(string $name): void
+ {
+    touch($name);
+ }
+
+ function createTempFile(string $name): void
+ {
+    touch('./temp/' . $name);
+ }

๐Ÿ” kembali ke atas

Hindari efek samping

Sebuah fungsi menghasilkan efek samping jika itu melakukan hal selain menerima nilai masuk dan mengembalikan nilai atau nilai-nilai lain. Sebuah efek samping dapat berupa menulis ke suatu file, memodifikasi beberapa variabel global, atau secara tidak sengaja mengirim seluruh uang kita ke orang asing.

Sekarang, kita perlu memiliki efek samping didalam program sesekali. Seperti contoh sebelumnya, kita mungkin perlu menulis ke suatu file. Apa yang ingin kita lakukan adalah memusatkan dimana kita melakukan ini. Jangan buat beberapa fungsi dan kelas yang menulis ke file tertentu. Miliki satu layanan yan melakukannya. Satu dan hanya satu.

Poin utamanya adalah menghindari jebakan umum seperti berbagi status antar objek tanpa struktur apapun, menggunakan tipe data yang dapat diubah yang dapat ditulis oleh apapun, dan tidak memusatkan dimana efek samping kita terjadi. Jika kita bisa melakukan ini, kita akan lebih bahagia daripada sebagian besar pemrogram lainnya.

Buruk:

// Variabel global  dirujuk oleh fungsi berikut.
// Jika kita memiliki fungsi lain yang menggunakan nama ini, sekarang itu akan menjadi array dan itu bisa merusaknya.
$name = 'Ryan McDermott';

function splitIntoFirstAndLastName(): void
{
    global $name;

    $name = explode(' ', $name);
}

splitIntoFirstAndLastName();

var_dump($name);
// ['Ryan', 'McDermott'];

Baik:

function splitIntoFirstAndLastName(string $name): array
{
    return explode(' ', $name);
}

$name = 'Ryan McDermott';
$newName = splitIntoFirstAndLastName($name);

var_dump($name);
// 'Ryan McDermott';

var_dump($newName);
// ['Ryan', 'McDermott'];
Pemfaktoran ulang:
- // Variabel global  dirujuk oleh fungsi berikut.
- // Jika kita memiliki fungsi lain yang menggunakan nama ini, sekarang itu akan menjadi array dan itu bisa merusaknya.
- $name = 'Ryan McDermott';

- function splitIntoFirstAndLastName(): void
+ function splitIntoFirstAndLastName(string $name): array
{
-    global $name;

-    $name = explode(' ', $name);
+    return explode(' ', $name);
}

+ $name = 'Ryan McDermott';
- splitIntoFirstAndLastName();
+ $newName = splitIntoFirstAndLastName($name);

var_dump($name);
- // ['Ryan', 'McDermott'];
+ // 'Ryan McDermott';

+ var_dump($newName);
+ // ['Ryan', 'McDermott'];

๐Ÿ” kembali ke atas

Jangan menulis ke fungsi global

Mencemari globals merupakan praktik yang buruk dalam banyak bahasa karena kita dapat berbenturan dengan library lain dan pengguna API kita tidak akan lebih bijak sampai mereka mendapatkan exception dalam produksi. Mari kita pikirkan sebuah contoh: bagaimana jika kita ingin memiliki susunan konfigurasi? Kita dapat menulis fungsi global seperti config(), namun itu dapat berbenturan dengan library lain yang mencoba melakukan hal yang sama.

Buruk:

function config(): array
{
    return [
        'foo' => 'bar',
    ];
}

Baik:

class Configuration
{
    private $configuration = [];

    public function __construct(array $configuration)
    {
        $this->configuration = $configuration;
    }

    public function get(string $key): ?string
    {
        // operator null coalescing
        return $this->configuration[$key] ?? null;
    }
}

Muat konfigurasi dan buat instance kelas Configuration

$configuration = new Configuration([
    'foo' => 'bar',
]);

Dan sekarang kita harus menggunakan instance Configuration dalam aplikasi kita.

Pemfaktoran ulang:
+ class Configuration
+ {
+    private $configuration = [];

+    public function __construct(array $configuration)
+    {
+        $this->configuration = $configuration;
+    }

+    public function get(string $key): ?string
+    {
+        // operator null coalescing
+        return $this->configuration[$key] ?? null;
+    }
+ }

- function config(): array
- {
-    return [
+ $configuration = new Configuration([
    'foo' => 'bar',
-    ];
- }
+ ]);

๐Ÿ” kembali ke atas

Jangan gunakan pola Singleton

Singleton merupakan sebuah anti-pattern. Diparafrasekan dari Brian Button:

  1. Mereka pada umumnya digunakan sebagai sebuah global instance, mengapa itu begitu buruk? Karena kita menyembunyikan dependensi dari aplikasi kita di kode kita, daripada mengeksposnya melalui interfaces. Membuat sesuatu global untuk menghindari menyebarkannya merupakan sebuah code smell.
  2. Mereka melanggar single responsibility principle: berdasarkan fakta bahwa mereka mengontrol kreasi dan siklus hidup mereka sendiri.
  3. Mereka secara bawaan menyebabkan kode menjadi highly coupled. Ini membuat memalsukan mereka saat diuji agak sulit dalam banyak kasus.
  4. Mereka membawa state kemana-mana selama seumur hidup aplikasi. Masalah lain untuk pengujian karena kita berakhir dengan situasi dimana tes perlu diurutkan dimana merupakan big no untuk unit pengujian. Mengapa? Karena setiap unit pengujian seharusnya independen dari yang lain.

Ada juga pemikiran bagus oleh Misko Hevery mengenai akar permasalahan.

Buruk:

class DBConnection
{
    private static $instance;

    private function __construct(string $dsn)
    {
        // ...
    }

    public static function getInstance(): self
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }

        return self::$instance;
    }

    // ...
}

$singleton = DBConnection::getInstance();

Baik:

class DBConnection
{
    public function __construct(string $dsn)
    {
        // ...
    }

    // ...
}

Buat instance dari kelas DBConnection dan konfigurasi itu dengan DSN.

$connection = new DBConnection($dsn);

Dan sekarang kita harus menggunakan instance dari DBConnection dalam aplikasi kita.

Pemfaktoran ulang:
class DBConnection
{
-    private static $instance;

-    private function __construct(string $dsn)
+    public function __construct(string $dsn)
    {
        // ...
    }

-    public static function getInstance(): self
-    {
-        if (self::$instance === null) {
-            self::$instance = new self();
-        }

-        return self::$instance;
-    }

    // ...
}

- $singleton = DBConnection::getInstance();
+ $connection = new DBConnection($dsn);

๐Ÿ” kembali ke atas

Enkapsulasi persyaratan

Buruk:

if ($article->state === 'published') {
    // ...
}

Baik:

if ($article->isPublished()) {
    // ...
}
Pemfaktoran ulang:
- if ($article->state === 'published') {
+ if ($article->isPublished()) {
    // ...
}

๐Ÿ” kembali ke atas

Hindari persyaratan negatif

Buruk:

function isDOMNodeNotPresent(DOMNode $node): bool
{
    // ...
}

if (! isDOMNodeNotPresent($node)) {
    // ...
}

Baik:

function isDOMNodePresent(DOMNode $node): bool
{
    // ...
}

if (isDOMNodePresent($node)) {
    // ...
}
Pemfaktoran ulang:
- function isDOMNodeNotPresent(DOMNode $node): bool
+ function isDOMNodePresent(DOMNode $node): bool
{
    // ...
}

- if (! isDOMNodeNotPresent($node)) {
+ if (isDOMNodePresent($node)) {
    // ...
}

๐Ÿ” kembali ke atas

Hindari persyaratan

This seems like an impossible task. Upon first hearing this, most people say, "how am I supposed to do anything without an if statement?" The answer is that you can use polymorphism to achieve the same task in many cases. The second question is usually, "well that's great but why would I want to do that?" The answer is a previous clean code concept we learned: a function should only do one thing. When you have classes and functions that have if statements, you are telling your user that your function does more than one thing. Remember, just do one thing.

Buruk:

class Airplane
{
    // ...

    public function getCruisingAltitude(): int
    {
        switch ($this->type) {
            case '777':
                return $this->getMaxAltitude() - $this->getPassengerCount();
            case 'Air Force One':
                return $this->getMaxAltitude();
            case 'Cessna':
                return $this->getMaxAltitude() - $this->getFuelExpenditure();
        }
    }
}

Baik:

interface Airplane
{
    // ...

    public function getCruisingAltitude(): int;
}

class Boeing777 implements Airplane
{
    // ...

    public function getCruisingAltitude(): int
    {
        return $this->getMaxAltitude() - $this->getPassengerCount();
    }
}

class AirForceOne implements Airplane
{
    // ...

    public function getCruisingAltitude(): int
    {
        return $this->getMaxAltitude();
    }
}

class Cessna implements Airplane
{
    // ...

    public function getCruisingAltitude(): int
    {
        return $this->getMaxAltitude() - $this->getFuelExpenditure();
    }
}
Pemfaktoran ulang:
- class Airplane
+ interface Airplane
{
    // ...

    public function getCruisingAltitude(): int
-    {
-        switch ($this->type) {
-            case '777':
-                return $this->getMaxAltitude() - $this->getPassengerCount();
-            case 'Air Force One':
-                return $this->getMaxAltitude();
-            case 'Cessna':
-                return $this->getMaxAltitude() - $this->getFuelExpenditure();
-        }
-    }
}

+ class Boeing777 implements Airplane
+ {
+    // ...

+    public function getCruisingAltitude(): int
+    {
+        return $this->getMaxAltitude() - $this->getPassengerCount();
+    }
+ }

+ class AirForceOne implements Airplane
+ {
+    // ...

+    public function getCruisingAltitude(): int
+    {
+        return $this->getMaxAltitude();
+    }
+ }

+ class Cessna implements Airplane
+ {
+    // ...

+    public function getCruisingAltitude(): int
+    {
+        return $this->getMaxAltitude() - $this->getFuelExpenditure();
+    }
+ }

๐Ÿ” kembali ke atas

Hindari pemeriksaan tipe data (bagian 1)

PHP tidak berjenis, yang berarti fungsi-fungsi kita dapat mengambil argumen dengan jenis apapun. Terkadang kita bisa terganggu oleh kebebasan ini dan menjadi cenderung untuk melakukan pemeriksaan tipe dalam fungsi-fungsi kita. Ada banyak cara untuk menghindari melakukan ini. Hal pertama yang perlu dipertimbangkan adalah API yang konsisten.

Buruk:

function travelToTexas($vehicle): void
{
    if ($vehicle instanceof Bicycle) {
        $vehicle->pedalTo(new Location('texas'));
    } elseif ($vehicle instanceof Car) {
        $vehicle->driveTo(new Location('texas'));
    }
}

Baik:

function travelToTexas(Vehicle $vehicle): void
{
    $vehicle->travelTo(new Location('texas'));
}
Pemfaktoran ulang:
- function travelToTexas($vehicle): void
+ function travelToTexas(Vehicle $vehicle): void
{
-    if ($vehicle instanceof Bicycle) {
-        $vehicle->pedalTo(new Location('texas'));
-    } elseif ($vehicle instanceof Car) {
-        $vehicle->driveTo(new Location('texas'));
-    }
+    $vehicle->travelTo(new Location('texas'));
}

๐Ÿ” kembali ke atas

Hindari pemeriksaan tipe data (bagian 2)

Jika kita bekerja dengan nilai-nilai primitif dasar seperti string, integer, dan array, serta menggunakan PHP 7+ dan kita tidak dapat menggunakan polimorfisme tetapi masih merasa perlu melakukan pemeriksaan tipe, kita sebaiknya mempertimbangkan deklarasi tipe atau mode ketat (strict mode). Ini memberikan kita tipe statis di atas sintaks PHP standar. Masalah dengan melakukan pemeriksaan tipe secara manual adalah bahwa melakukannya akan memerlukan banyak kata-kata tambahan sehingga "keamanan tipe" palsu yang kita dapatkan tidak sebanding dengan keterbacaan yang hilang. Pertahankan kebersihan kode PHP kita, tulis pengujian yang baik, dan lakukan tinjauan kode yang baik. Atau, lakukan semua itu tetapi dengan deklarasi tipe ketat PHP atau mode ketat.

Buruk:

function combine($val1, $val2): int
{
    if (! is_numeric($val1) || ! is_numeric($val2)) {
        throw new Exception('Must be of type Number');
    }

    return $val1 + $val2;
}

Baik:

function combine(int $val1, int $val2): int
{
    return $val1 + $val2;
}
Pemfaktoran ulang:
- function combine($val1, $val2): int
+ function combine(int $val1, int $val2): int
{
-    if (! is_numeric($val1) || ! is_numeric($val2)) {
-        throw new Exception('Must be of type Number');
-    }

    return $val1 + $val2;
}

๐Ÿ” kembali ke atas

Hapus kode mati

Kode mati sama buruknya dengan kode duplikat. Tidak ada alasan untuk tetap menyimpannya dalam basis kode kita. Jika kode tersebut tidak dipanggil, buanglah! Kode tersebut masih akan aman dalam riwayat versi kita jika kita masih memerlukannya.

Buruk:

function oldRequestModule(string $url): void
{
    // ...
}

function newRequestModule(string $url): void
{
    // ...
}

$request = newRequestModule($requestUrl);
inventoryTracker('apples', $request, 'www.inventory-awesome.io');

Baik:

function requestModule(string $url): void
{
    // ...
}

$request = requestModule($requestUrl);
inventoryTracker('apples', $request, 'www.inventory-awesome.io');
Pemfaktoran ulang:
- function oldRequestModule(string $url): void
+ function requestModule(string $url): void
{
    // ...
}

- function newRequestModule(string $url): void
- {
-    // ...
- }

- $request = newRequestModule($requestUrl);
+ $request = requestModule($requestUrl);
inventoryTracker('apples', $request, 'www.inventory-awesome.io');

๐Ÿ” kembali ke atas

Objek dan Struktur Data

Gunakan enkapsulasi objek

Di dalam PHP, kita dapat menggunakan kata kunci public, protected, dan private untuk metode-metode. Dengan menggunakan ini, kita dapat mengontrol modifikasi properti pada suatu objek.

  • Ketika kita ingin melakukan lebih dari sekadar mengambil properti objek, kita tidak perlu mencari dan mengubah setiap aksesornya dalam basis kode kita.
  • Memudahkan penambahan validasi saat melakukan operasi set.
  • Melindungi representasi internal.
  • Mudah untuk menambahkan pencatatan (logging) dan penanganan kesalahan (error handling) saat mengambil dan mengatur nilai.
  • Dengan mewarisi kelas ini, kita dapat mengganti (override) fungsionalitas default.
  • Kita dapat memuat properti objek secara terlambat (lazy loading), misalnya mendapatkannya dari server.

Selain itu, ini adalah bagian dari prinsip Open/Closed.

Buruk:

class BankAccount
{
    public $balance = 1000;
}

$bankAccount = new BankAccount();

// Beli sepatu ...
$bankAccount->balance -= 100;

Baik:

class BankAccount
{
    private $balance;

    public function __construct(int $balance = 1000)
    {
      $this->balance = $balance;
    }

    public function withdraw(int $amount): void
    {
        if ($amount > $this->balance) {
            throw new \Exception('Amount greater than available balance.');
        }

        $this->balance -= $amount;
    }

    public function deposit(int $amount): void
    {
        $this->balance += $amount;
    }

 ย  ย public function getBalance(): int
    {
        return $this->balance;
    }
}

$bankAccount = new BankAccount();

// Beli sepatu ...
$bankAccount->withdraw($shoesPrice);

// Mengambil balance
$balance = $bankAccount->getBalance();
Pemfaktoran ulang:
class BankAccount
{
-    public $balance = 1000;
+    private $balance;

+    public function __construct(int $balance = 1000)
+    {
+      $this->balance = $balance;
+    }

+    public function withdraw(int $amount): void
+    {
+        if ($amount > $this->balance) {
+            throw new \Exception('Amount greater than available balance.');
+        }

+        $this->balance -= $amount;
+    }

+    public function deposit(int $amount): void
+    {
+        $this->balance += $amount;
+    }

+ ย  ย public function getBalance(): int
+    {
+        return $this->balance;
+    }
}

$bankAccount = new BankAccount();

// Beli sepatu ...
- $bankAccount->balance -= 100;
+ $bankAccount->withdraw($shoesPrice);

+ // Mengambil balance
+ $balance = $bankAccount->getBalance();

๐Ÿ” kembali ke atas

Buat objek memiliki anggota pribadi/dilindungi

  • Metode-metode dan properti public paling berisiko untuk diubah, karena beberapa kode dari luar mungkin sangat mengandalkan mereka dan kita tidak dapat mengendalikan kode mana yang mengandalkan mereka. Modifikasi pada kelas berisiko untuk semua pengguna kelas tersebut.
  • Modifier protected sama berisikonya dengan publik, karena mereka tersedia dalam ruang lingkup setiap kelas turunan. Ini berarti bahwa perbedaan antara publik dan terlindungi hanya pada mekanisme akses, tetapi jaminan penyembunyian tetap sama. Modifikasi pada kelas berisiko untuk semua kelas turunan.
  • Modifier private menjamin bahwa kode berisiko diubah hanya dalam batas-batas satu kelas (kita aman untuk modifikasi dan kita tidak akan mengalami efek Jenga).

Oleh karena itu, gunakan private secara default dan public/protected saat kita perlu memberikan akses untuk kelas eksternal.

Untuk informasi lebih lanjut, kita dapat membaca artikel blog tentang topik ini yang ditulis oleh Fabien Potencier.

Buruk:

class Employee
{
    public $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }
}

$employee = new Employee('John Doe');
// Nama karyawan: John Doe
echo 'Employee name: ' . $employee->name;

Baik:

class Employee
{
    private $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }

    public function getName(): string
    {
        return $this->name;
    }
}

$employee = new Employee('John Doe');
// Nama karyawan: John Doe
echo 'Employee name: ' . $employee->getName();
Pemfaktoran ulang:
class Employee
{
-    public $name;
+    private $name;

    public function __construct(string $name)
    {
        $this->name = $name;
    }

+    public function getName(): string
+    {
+        return $this->name;
+    }
}

$employee = new Employee('John Doe');
// Nama karyawan: John Doe
- echo 'Employee name: ' . $employee->name;
+ echo 'Employee name: ' . $employee->getName();

๐Ÿ” kembali ke atas

Kelas

Pilih composition daripada inheritance

Seperti yang terkenal dijelaskan dalam Design Patterns oleh the Gang of Four, kita sebaiknya lebih memilih composition daripada inheritance bila memungkinkan. Ada banyak alasan baik untuk menggunakan inheritance dan banyak alasan baik untuk menggunakan composition. Titik utama dari prinsip ini adalah jika pikiran kita secara naluriah mengarah ke inheritance, cobalah berpikir apakah composition dapat lebih baik memodelkan masalah kita. Dalam beberapa kasus, ini bisa berhasil.

Kita mungkin bertanya, "kapan sebaiknya kita menggunakan inheritance?" Ini tergantung pada masalah yang sedang kita hadapi, tetapi berikut adalah daftar yang cukup baik kapan inheritance lebih masuk akal daripada composition:

  1. Inheritance kita mewakili hubungan "adalah" bukan hubungan "memiliki" (Manusia->Hewan vs. Pengguna->RincianPengguna).
  2. Kita dapat menggunakan kembali kode dari kelas dasar (Manusia dapat bergerak seperti semua hewan).
  3. Kita ingin melakukan perubahan global pada kelas-kelas turunan dengan mengubah kelas dasar. (Ubah pengeluaran kalori semua hewan saat mereka bergerak).

Buruk:

class Employee
{
    private $name;

    private $email;

    public function __construct(string $name, string $email)
    {
        $this->name = $name;
        $this->email = $email;
    }

    // ...
}

// Buruk karena Employees memiliki data pajak
// EmployeeTaxData bukanlah tipe dari Employee

class EmployeeTaxData extends Employee
{
    private $ssn;

    private $salary;

    public function __construct(string $name, string $email, string $ssn, string $salary)
    {
        parent::__construct($name, $email);

        $this->ssn = $ssn;
        $this->salary = $salary;
    }

    // ...
}

Baik:

class EmployeeTaxData
{
    private $ssn;

    private $salary;

    public function __construct(string $ssn, string $salary)
    {
        $this->ssn = $ssn;
        $this->salary = $salary;
    }

    // ...
}

class Employee
{
    private $name;

    private $email;

    private $taxData;

    public function __construct(string $name, string $email)
    {
        $this->name = $name;
        $this->email = $email;
    }

    public function setTaxData(EmployeeTaxData $taxData): void
    {
        $this->taxData = $taxData;
    }

    // ...
}
Pemfaktoran ulang:
+ class EmployeeTaxData
+ {
+    private $ssn;

+    private $salary;

+    public function __construct(string $ssn, string $salary)
+    {
+        $this->ssn = $ssn;
+        $this->salary = $salary;
+    }

+    // ...
+ }

class Employee
{
    private $name;

    private $email;

+    private $taxData;

    public function __construct(string $name, string $email)
    {
        $this->name = $name;
        $this->email = $email;
    }

+    public function setTaxData(EmployeeTaxData $taxData): void
+    {
+        $this->taxData = $taxData;
+    }

    // ...
}

- // Buruk karena Employees memiliki data pajak
- // EmployeeTaxData bukanlah tipe dari Employee


- class EmployeeTaxData extends Employee
- {
-    private $ssn;

-    private $salary;

-    public function __construct(string $name, string $email, string $ssn, string $salary)
-    {
-        parent::__construct($name, $email);

-        $this->ssn = $ssn;
-        $this->salary = $salary;
-    }

-    // ...
-}

๐Ÿ” kembali ke atas

Hindari fluent interfaces

Suatu Fluent interface adalah sebuah API berorientasi objek yang bertujuan untuk meningkatkan keterbacaan kode sumber dengan menggunakan Method chaining.

Meskipun ada beberapa konteks, sering kali objek pembangun (builder objects), di mana pola ini mengurangi kerumitan kode (misalnya PHPUnit Mock Builder atau Doctrine Query Builder), seringkali hal ini datang dengan beberapa biaya:

  1. Melanggar Enkapsulasi.
  2. Melanggar Decorator.
  3. Sulit untuk mock dalam suite pengujian.
  4. Membuat perbedaan antara commit sulit dibaca.

Untuk informasi lebih lanjut, kita dapat membaca artikel blog lengkap tentang topik ini yang ditulis oleh Marco Pivetta.

Buruk:

class Car
{
    private $make = 'Honda';

    private $model = 'Accord';

    private $color = 'white';

    public function setMake(string $make): self
    {
        $this->make = $make;

        // NOTE: Mengembalikan this untuk chaining
        return $this;
    }

    public function setModel(string $model): self
    {
        $this->model = $model;

        // NOTE: Mengembalikan this untuk chaining
        return $this;
    }

    public function setColor(string $color): self
    {
        $this->color = $color;

        // NOTE: Mengembalikan this untuk chaining
        return $this;
    }

    public function dump(): void
    {
        var_dump($this->make, $this->model, $this->color);
    }
}

$car = (new Car())
    ->setColor('pink')
    ->setMake('Ford')
    ->setModel('F-150')
    ->dump();

Baik:

class Car
{
    private $make = 'Honda';

    private $model = 'Accord';

    private $color = 'white';

    public function setMake(string $make): void
    {
        $this->make = $make;
    }

    public function setModel(string $model): void
    {
        $this->model = $model;
    }

    public function setColor(string $color): void
    {
        $this->color = $color;
    }

    public function dump(): void
    {
        var_dump($this->make, $this->model, $this->color);
    }
}

$car = new Car();
$car->setColor('pink');
$car->setMake('Ford');
$car->setModel('F-150');
$car->dump();
Pemfaktoran ulang:
class Car
{
    private $make = 'Honda';

    private $model = 'Accord';

    private $color = 'white';

-    public function setMake(string $make): self
+    public function setMake(string $make): void
    {
        $this->make = $make;

-        // NOTE: Mengembalikan this untuk chaining
-        return $this;
    }

-    public function setModel(string $model): self
+    public function setModel(string $model): void
    {
        $this->model = $model;

-        // NOTE: Mengembalikan this untuk chaining
-        return $this;
    }

-    public function setColor(string $color): self
+    public function setColor(string $color): void
    {
        $this->color = $color;

-        // NOTE: Mengembalikan this untuk chaining
-        return $this;
    }

    public function dump(): void
    {
        var_dump($this->make, $this->model, $this->color);
    }
}

- $car = (new Car())
-    ->setColor('pink')
-    ->setMake('Ford')
-    ->setModel('F-150')
-    ->dump();
+ $car = new Car();
+ $car->setColor('pink');
+ $car->setMake('Ford');
+ $car->setModel('F-150');
+ $car->dump();

๐Ÿ” kembali ke atas

Pilih kelas final

Kata kunci final sebaiknya digunakan setiap kali memungkinkan:

  1. Ini mencegah rantai inheritance yang tidak terkontrol.
  2. Ini mendorong composition.
  3. Ini mendorong Prinsip Tanggung Jawab Tunggal (Single Responsibility Principle).
  4. Ini mendorong pengembang untuk menggunakan metode-metode public kita daripada memperluas kelas untuk mendapatkan akses ke metode-metode protected.
  5. Ini memungkinkan kita untuk mengubah kode kita tanpa merusak aplikasi yang menggunakan kelas kita.

Satu-satunya syarat adalah kelas kita seharusnya mengimplementasikan sebuah antarmuka dan tidak ada metode-metode publik lain yang didefinisikan.

Untuk informasi lebih lanjut, kita dapat membaca artikel blog tentang topik ini yang ditulis oleh Marco Pivetta (Ocramius).

Buruk:

final class Car
{
    private $color;

    public function __construct($color)
    {
        $this->color = $color;
    }

    /**
     * @return string The color of the vehicle
     */
    public function getColor()
    {
        return $this->color;
    }
}

Baik:

interface Vehicle
{
    /**
     * @return string The color of the vehicle
     */
    public function getColor();
}

final class Car implements Vehicle
{
    private $color;

    public function __construct($color)
    {
        $this->color = $color;
    }

    public function getColor()
    {
        return $this->color;
    }
}
Pemfaktoran ulang:
+ interface Vehicle
+ {
+    /**
+     * @return string The color of the vehicle
+     */
+    public function getColor();
+ }

- final class Car
+ final class Car implements Vehicle
{
    private $color;

    public function __construct($color)
    {
        $this->color = $color;
    }

-    /**
-     * @return string The color of the vehicle
-     */
    public function getColor()
    {
        return $this->color;
    }
}

๐Ÿ” kembali ke atas

SOLID

SOLID adalah akronim mnemonik yang diperkenalkan oleh Michael Feathers untuk lima prinsip pertama yang dinamai oleh Robert Martin, yang berarti lima prinsip dasar pemrograman dan desain berorientasi objek.

Single Responsibility Principle (SRP)

Seperti yang dinyatakan dalam Clean Code,

"There should never be more than one reason for a class to change".
"Tidak boleh ada lebih dari satu alasan bagi sebuah kelas untuk berubah".

Sangat menggoda untuk memadatkan sebuah kelas dengan banyak fungsionalitas, seperti ketika kita hanya dapat membawa satu koper dalam penerbangan kita. Masalahnya adalah bahwa kelas kita tidak akan menjadi konseptual yang kohesif dan akan memberikan banyak alasan bagi perubahan. Meminimalkan jumlah kali kita perlu mengubah sebuah kelas itu penting. Hal ini penting karena jika terlalu banyak fungsionalitas dalam satu kelas dan kita memodifikasi bagian dari itu, bisa sulit untuk memahami bagaimana hal tersebut akan memengaruhi modul-modul tergantung lainnya dalam kode sumber kita.

Buruk:

class UserSettings
{
    private $user;

    public function __construct(User $user)
    {
        $this->user = $user;
    }

    public function changeSettings(array $settings): void
    {
        if ($this->verifyCredentials()) {
            // ...
        }
    }

    private function verifyCredentials(): bool
    {
        // ...
    }
}

Baik:

class UserAuth
{
    private $user;

    public function __construct(User $user)
    {
        $this->user = $user;
    }

    public function verifyCredentials(): bool
    {
        // ...
    }
}

class UserSettings
{
    private $user;

    private $auth;

    public function __construct(User $user)
    {
        $this->user = $user;
        $this->auth = new UserAuth($user);
    }

    public function changeSettings(array $settings): void
    {
        if ($this->auth->verifyCredentials()) {
            // ...
        }
    }
}
Pemfaktoran ulang:
+ class UserAuth
+ {
+    private $user;

+    public function __construct(User $user)
+    {
+        $this->user = $user;
+    }

+    public function verifyCredentials(): bool
+    {
+        // ...
+    }
+ }

class UserSettings
{
    private $user;

+    private $auth;

    public function __construct(User $user)
    {
        $this->user = $user;
+        $this->auth = new UserAuth($user);        
    }

    public function changeSettings(array $settings): void
    {
-        if ($this->verifyCredentials()) {
+        if ($this->auth->verifyCredentials()) {
            // ...
        }
    }

-    private function verifyCredentials(): bool
-    {
-        // ...
-    }
}

๐Ÿ” kembali ke atas

Open/Closed Principle (OCP)

Seperti yang dinyatakan oleh Bertrand Meyer, "entitas-entitas perangkat lunak (kelas, modul, fungsi, dll.) sebaiknya terbuka untuk perluasan, tetapi tertutup untuk modifikasi." Namun apa artinya itu? Prinsip ini pada dasarnya menyatakan bahwa kita sebaiknya memungkinkan pengguna untuk menambahkan fungsionalitas baru tanpa mengubah kode yang sudah ada.

Buruk:

abstract class Adapter
{
    protected $name;

    public function getName(): string
    {
        return $this->name;
    }
}

class AjaxAdapter extends Adapter
{
    public function __construct()
    {
        parent::__construct();

        $this->name = 'ajaxAdapter';
    }
}

class NodeAdapter extends Adapter
{
    public function __construct()
    {
        parent::__construct();

        $this->name = 'nodeAdapter';
    }
}

class HttpRequester
{
    private $adapter;

    public function __construct(Adapter $adapter)
    {
        $this->adapter = $adapter;
    }

    public function fetch(string $url): Promise
    {
        $adapterName = $this->adapter->getName();

        if ($adapterName === 'ajaxAdapter') {
            return $this->makeAjaxCall($url);
        } elseif ($adapterName === 'httpNodeAdapter') {
            return $this->makeHttpCall($url);
        }
    }

    private function makeAjaxCall(string $url): Promise
    {
        // request and return promise
    }

    private function makeHttpCall(string $url): Promise
    {
        // request and return promise
    }
}

Baik:

interface Adapter
{
    public function request(string $url): Promise;
}

class AjaxAdapter implements Adapter
{
    public function request(string $url): Promise
    {
        // request and return promise
    }
}

class NodeAdapter implements Adapter
{
    public function request(string $url): Promise
    {
        // request and return promise
    }
}

class HttpRequester
{
    private $adapter;

    public function __construct(Adapter $adapter)
    {
        $this->adapter = $adapter;
    }

    public function fetch(string $url): Promise
    {
        return $this->adapter->request($url);
    }
}
Pemfaktoran ulang:
- abstract class Adapter
+ interface Adapter
{
-    protected $name;

-    public function getName(): string
-    {
-        return $this->name;
-    }

+    public function request(string $url): Promise;
}

- class AjaxAdapter extends Adapter
+ class AjaxAdapter implements Adapter
{
-    public function __construct()
-    {
-        parent::__construct();

-        $this->name = 'ajaxAdapter';
-    }

+    public function request(string $url): Promise
+    {
+        // request and return promise
+    }
}

- class NodeAdapter extends Adapter
+ class NodeAdapter implements Adapter
{
-    public function __construct()
-    {
-        parent::__construct();

-        $this->name = 'nodeAdapter';
-    }

+    public function request(string $url): Promise
+    {
+        // request and return promise
+    }
}

class HttpRequester
{
    private $adapter;

    public function __construct(Adapter $adapter)
    {
        $this->adapter = $adapter;
    }

    public function fetch(string $url): Promise
    {
-        $adapterName = $this->adapter->getName();

-        if ($adapterName === 'ajaxAdapter') {
-            return $this->makeAjaxCall($url);
-        } elseif ($adapterName === 'httpNodeAdapter') {
-            return $this->makeHttpCall($url);
-        }
+        return $this->adapter->request($url);
    }

-    private function makeAjaxCall(string $url): Promise
-    {
-        // request and return promise
-    }

-    private function makeHttpCall(string $url): Promise
-    {
-        // request and return promise
-    }
}

๐Ÿ” kembali ke atas

Liskov Substitution Principle (LSP)

Ini adalah istilah yang cukup menakutkan untuk konsep yang sangat sederhana. Secara formal didefinisikan sebagai "Jika S adalah subjenis dari T, maka objek tipe T dapat diganti dengan objek tipe S (yaitu, objek tipe S dapat menggantikan objek tipe T) tanpa mengubah properti yang diinginkan dari program tersebut (kebenaran, tugas yang dilakukan, dll.)." Itu adalah definisi yang bahkan lebih menakutkan.

Penjelasan terbaik untuk ini adalah jika kita memiliki kelas induk dan kelas anak, maka kelas dasar dan kelas anak dapat digunakan secara bergantian tanpa menghasilkan hasil yang salah. Ini mungkin masih membingungkan, jadi mari kita lihat contoh klasik Persegi-Persegi Panjang contohnya. Secara matematis, persegi adalah persegi panjang, tetapi jika kita memodelkannya menggunakan hubungan "adalah" melalui pewarisan, kita dengan cepat akan menghadapi masalah.

Buruk:

class Rectangle
{
    protected $width = 0;

    protected $height = 0;

    public function setWidth(int $width): void
    {
        $this->width = $width;
    }

    public function setHeight(int $height): void
    {
        $this->height = $height;
    }

    public function getArea(): int
    {
        return $this->width * $this->height;
    }
}

class Square extends Rectangle
{
    public function setWidth(int $width): void
    {
        $this->width = $this->height = $width;
    }

    public function setHeight(int $height): void
    {
        $this->width = $this->height = $height;
    }
}

function printArea(Rectangle $rectangle): void
{
    $rectangle->setWidth(4);
    $rectangle->setHeight(5);

    // Buruk: Akan mengembalikan 25 untuk Square. Seharusnya 20.
    echo sprintf('%s has area %d.', get_class($rectangle), $rectangle->getArea()) . PHP_EOL;
}

$rectangles = [new Rectangle(), new Square()];

foreach ($rectangles as $rectangle) {
    printArea($rectangle);
}

Baik:

Cara terbaik adalah memisahkan empat persegi dan alokasi subjenis yang lebih umum untuk kedua bentuk tersebut.

Meskipun ada kesamaan yang jelas antara persegi dan persegi panjang, keduanya berbeda. Sebuah persegi memiliki banyak kesamaan dengan belah ketupat, dan persegi panjang dengan jajar genjang, tetapi mereka bukan subjenis. Sebuah persegi, persegi panjang, belah ketupat, dan jajar genjang adalah bentuk-bentuk terpisah dengan properti masing-masing, meskipun serupa.

interface Shape
{
    public function getArea(): int;
}

class Rectangle implements Shape
{
    private $width = 0;
    private $height = 0;

    public function __construct(int $width, int $height)
    {
        $this->width = $width;
        $this->height = $height;
    }

    public function getArea(): int
    {
        return $this->width * $this->height;
    }
}

class Square implements Shape
{
    private $length = 0;

    public function __construct(int $length)
    {
        $this->length = $length;
    }

    public function getArea(): int
    {
 ย  ย  ย  ย return $this->length ** 2;
 ย  ย }
}

function printArea(Shape $shape): void
{
    echo sprintf('%s has area %d.', get_class($shape), $shape->getArea()).PHP_EOL;
}

$shapes = [new Rectangle(4, 5), new Square(5)];

foreach ($shapes as $shape) {
    printArea($shape);
}
Pemfaktoran ulang:
+ interface Shape
+ {
+    public function getArea(): int;
+ }

- class Rectangle
+ class Rectangle implements Shape
{
-    protected $width = 0;
+    private $width = 0;
-    protected $height = 0;
+    private $height = 0;

-    public function setWidth(int $width): void
+    public function __construct(int $width, int $height)
    {
        $this->width = $width;
-    }

-    public function setHeight(int $height): void
-    {
        $this->height = $height;
    }

    public function getArea(): int
    {
        return $this->width * $this->height;
    }
}

- class Square extends Rectangle
+ class Square implements Shape
{
+    private $length = 0;

-    public function setWidth(int $width): void
-    {
-        $this->width = $this->height = $width;
-    }

-    public function setHeight(int $height): void
-    {
-        $this->width = $this->height = $height;
-    }

+    public function __construct(int $length)
+    {
+        $this->length = $length;
+    }

+    public function getArea(): int
+    {
+ ย  ย  ย  ย return $this->length ** 2;
+ ย  ย }
}

- function printArea(Rectangle $rectangle): void
+ function printArea(Shape $shape): void
{
-    $rectangle->setWidth(4);
-    $rectangle->setHeight(5);

-    // Buruk: Akan mengembalikan 25 untuk Square. Seharusnya 20.
-    echo sprintf('%s has area %d.', get_class($rectangle), $rectangle->getArea()).PHP_EOL;
+    echo sprintf('%s has area %d.', get_class($shape), $shape->getArea()).PHP_EOL;
}

- $rectangles = [new Rectangle(), new Square()];
+ $shapes = [new Rectangle(4, 5), new Square(5)];

- foreach ($rectangles as $rectangle) {
+ foreach ($shapes as $shape) {
-    printArea($rectangle);
+    printArea($shape);
}

๐Ÿ” kembali ke atas

Interface Segregation Principle (ISP)

ISP menyatakan bahwa

"Clients should not be forced to depend upon interfaces that they do not use."
"Klien seharusnya tidak dipaksa untuk bergantung pada antarmuka yang tidak mereka gunakan."

Contoh yang baik untuk dilihat yang mendemonstrasikan prinsip ini adalah untuk kelas-kelas yang memerlukan objek pengaturan besar. Tidak mengharuskan klien mengatur banyak pilihan yang besar adalah hal yang menguntungkan, karena sebagian besar waktu mereka tidak akan memerlukan semua pengaturan tersebut. Membuatnya opsional membantu mencegah adanya antarmuka yang besar (fat interface).

Buruk:

interface Employee
{
    public function work(): void;

    public function eat(): void;
}

class HumanEmployee implements Employee
{
    public function work(): void
    {
        // ....bekerja
    }

    public function eat(): void
    {
        // ......makan saat istirahat jam makan siang
    }
}

class RobotEmployee implements Employee
{
    public function work(): void
    {
        //....bekerja lebih banyak lagi
    }

    public function eat(): void
    {
        //....robot tak bisa makan, tapi harus implementasikan metode ini
    }
}

Baik:

Not every worker is an employee, but every employee is a worker.

interface Workable
{
    public function work(): void;
}

interface Feedable
{
    public function eat(): void;
}

interface Employee extends Feedable, Workable
{
}

class HumanEmployee implements Employee
{
    public function work(): void
    {
        // ....bekerja
    }

    public function eat(): void
    {
        //....makan saat istirahat jam makan siang
    }
}

// robot hanya dapat bekerja
class RobotEmployee implements Workable
{
    public function work(): void
    {
        // ....bekerja
    }
}
Pemfaktoran ulang:
- interface Employee
+ interface Workable
{
    public function work(): void;
+ }
+
+ interface Feedable
+ {
    public function eat(): void;
}

+ interface Employee extends Feedable, Workable
+ {
+ }

class HumanEmployee implements Employee
{
    public function work(): void
    {
        // ....bekerja
    }

    public function eat(): void
    {
        // ......makan saat istirahat jam makan siang
    }
}

+ // robot hanya dapat bekerja
- class RobotEmployee implements Employee
+ class RobotEmployee implements Workable
{
    public function work(): void
    {
        //....bekerja lebih banyak lagi
    }

-    public function eat(): void
-    {
-        //....robot tak bisa makan, tapi harus implementasikan metode ini
-    }
}

๐Ÿ” kembali ke atas

Dependency Inversion Principle (DIP)

Prinsip ini menyatakan dua hal penting:

  1. Modul tingkat tinggi seharusnya tidak bergantung pada modul tingkat rendah. Keduanya seharusnya bergantung pada abstraksi.
  2. Abstraksi seharusnya tidak bergantung pada detail. Detail seharusnya bergantung pada abstraksi.

Ini mungkin sulit dipahami pada awalnya, tetapi jika kita telah bekerja dengan kerangka kerja PHP (seperti Symfony), kita telah melihat implementasi dari prinsip ini dalam bentuk Dependency Injection (DI). Meskipun mereka bukan konsep yang identik, DIP mencegah modul tingkat tinggi mengetahui detail dari modul tingkat rendahnya dan mengatur mereka. Hal ini dapat dicapai melalui DI. Manfaat besar dari ini adalah mengurangi ketergantungan (coupling) antara modul-modul tersebut. Ketergantungan (coupling) adalah pola pengembangan yang sangat buruk karena membuat kode kita sulit di faktor ulang (refactor).

Buruk:

class Employee
{
    public function work(): void
    {
        // ....bekerja
    }
}

class Robot extends Employee
{
    public function work(): void
    {
        //....bekerja lebih banyak lagi
    }
}

class Manager
{
    private $employee;

    public function __construct(Employee $employee)
    {
        $this->employee = $employee;
    }

    public function manage(): void
    {
        $this->employee->work();
    }
}

Baik:

interface Employee
{
    public function work(): void;
}

class Human implements Employee
{
    public function work(): void
    {
        // ....bekerja
    }
}

class Robot implements Employee
{
    public function work(): void
    {
        //....bekerja lebih banyak lagi
    }
}

class Manager
{
    private $employee;

    public function __construct(Employee $employee)
    {
        $this->employee = $employee;
    }

    public function manage(): void
    {
        $this->employee->work();
    }
}
Pemfaktoran ulang:
- class Employee
+ interface Employee
{
-    public function work(): void
+    public function work(): void;
-    {
-        // ....bekerja
-    }
}

- class Robot extends Employee
+ class Robot implements Employee
{
    public function work(): void
    {
        //....bekerja lebih banyak lagi
    }
}

class Manager
{
    private $employee;

    public function __construct(Employee $employee)
    {
        $this->employee = $employee;
    }

    public function manage(): void
    {
        $this->employee->work();
    }
}

๐Ÿ” kembali ke atas

Donโ€™t Repeat Yourself (DRY)

Coba perhatikan prinsip DRY (Don't Repeat Yourself).

Lakukan yang terbaik untuk menghindari kode duplikat. Kode duplikat itu buruk karena berarti ada lebih dari satu tempat untuk mengubah sesuatu jika kita perlu mengubah beberapa logika.

Bayangkan jika kita menjalankan restoran dan kita melacak inventaris kita: semua tomat, bawang, bawang putih, rempah-rempah, dll. Jika kita memiliki beberapa daftar untuk ini, maka semuanya harus diperbarui ketika kita menyajikan hidangan dengan tomat di dalamnya. Jika kita hanya memiliki satu daftar, hanya ada satu tempat untuk memperbarui!

Seringkali kita memiliki kode duplikat karena kita memiliki dua atau lebih hal yang sedikit berbeda, yang memiliki banyak kesamaan, tetapi perbedaan mereka memaksa kita untuk memiliki dua atau lebih fungsi terpisah yang melakukan banyak hal yang sama. Menghilangkan kode duplikat berarti membuat abstraksi yang dapat menangani set berbagai hal ini hanya dengan satu fungsi/modul/kelas.

Membuat abstraksi yang tepat sangat penting, itulah mengapa kita sebaiknya mengikuti prinsip SOLID yang diuraikan di bagian Kelas. Abstraksi yang buruk dapat lebih buruk daripada kode duplikat, jadi berhati-hatilah! Setelah mengatakan ini, jika kita dapat membuat abstraksi yang baik, lakukanlah! Jangan mengulangi diri kita sendiri (Don't Repeat Yourself), jika tidak kita akan menemukan diri kita mengubah beberapa tempat setiap kali kita ingin mengubah satu hal.

Buruk:

function showDeveloperList(array $developers): void
{
    foreach ($developers as $developer) {
        $expectedSalary = $developer->calculateExpectedSalary();
        $experience = $developer->getExperience();
        $githubLink = $developer->getGithubLink();
        $data = [$expectedSalary, $experience, $githubLink];

        render($data);
    }
}

function showManagerList(array $managers): void
{
    foreach ($managers as $manager) {
        $expectedSalary = $manager->calculateExpectedSalary();
        $experience = $manager->getExperience();
        $githubLink = $manager->getGithubLink();
        $data = [$expectedSalary, $experience, $githubLink];

        render($data);
    }
}

Baik:

function showList(array $employees): void
{
    foreach ($employees as $employee) {
        $expectedSalary = $employee->calculateExpectedSalary();
        $experience = $employee->getExperience();
        $githubLink = $employee->getGithubLink();
        $data = [$expectedSalary, $experience, $githubLink];

        render($data);
    }
}

Very good:

Lebih baik menggunakan versi kode yang ringkas.

function showList(array $employees): void
{
    foreach ($employees as $employee) {
        render([$employee->calculateExpectedSalary(), $employee->getExperience(), $employee->getGithubLink()]);
    }
}
Pemfaktoran ulang:
- function showDeveloperList(array $developers): void
- {
-    foreach ($developers as $developer) {
-        $expectedSalary = $developer->calculateExpectedSalary();
-        $experience = $developer->getExperience();
-        $githubLink = $developer->getGithubLink();
-        $data = [$expectedSalary, $experience, $githubLink];

-        render($data);
-    }
- }

- function showManagerList(array $managers): void
- {
-    foreach ($managers as $manager) {
-        $expectedSalary = $manager->calculateExpectedSalary();
-        $experience = $manager->getExperience();
-        $githubLink = $manager->getGithubLink();
-        $data = [$expectedSalary, $experience, $githubLink];

-        render($data);
-    }
- }

+ function showList(array $employees): void
+ {
+    foreach ($employees as $employee) {
-        $expectedSalary = $employee->calculateExpectedSalary();
-        $experience = $employee->getExperience();
-        $githubLink = $employee->getGithubLink();
-        $data = [$expectedSalary, $experience, $githubLink];

-        render($data);
+        render([$employee->calculateExpectedSalary(), $employee->getExperience(), $employee->getGithubLink()]);
+    }
+ }

๐Ÿ” kembali ke atas

Terjemahan

This is also available in other languages:

๐Ÿ” kembali ke atas

Releases

No releases published

Packages

No packages published

Languages

  • PHP 100.0%