216
221

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

PHP: Clean Code (clean-code-php) 蜜柑薬

Last updated at Posted at 2017-09-02

もくじ

  1. はじめに
  2. 変数
  3. 関数
  4. オブジェクトとデータ構造
  5. クラス
    1. S: 単一責任の原則 (SRP)
    2. O: オープン/クローズドの原則 (OCP)
    3. L: リスコフの置換原則 (LSP)
    4. I: インターフェイス分離の原則 (ISP)
    5. D: 依存逆転の法則 (DIP)
  6. 同じことを繰り返すな (DRY)

はじめに

この記事はRobert C. Martinの本「Clean Code」のソフトウェアエンジニアリングの法則をPHPに適合させたものです。これはスタイルガイドではありません。読みやすく、再利用しやすく、そしてリファクタリングしやすいPHPコードを書くためのガイドです。

ここで挙げられるすべての原則は厳密に守らなくてはいけないわけではなく、少し守らなかったところで一般には許容されます。あくまでガイドラインですが、Clean Codeの著者たちがみな長年に渡って経験してきたことです。

この記事(clean-code-php)はclean-code-javascriptに触発されました。

翻訳について

この記事はclean-code-phpうさみけんた@tadsan雑に日本語に訳したものです。 (元のリビジョンは1db3e53です。)

記事の翻訳作業は2017年9月3日時点で未完了です。半分程度まで訳しました。
やる気が残ってたら、明日か後日には、また続きを訳します。

原文の著作権表記およびライセンス許諾は以下の文です。

The MIT License (MIT)

Copyright (c) 2016 Ryan McDermott

Permission is hereby granted, free of charge, to any person obtaining a copy

of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE

訳者は原著「Clean Code」および翻訳書は未読です、訳語はWebで一般に見られるもの、あるいは独自で宛てたものです。

また、これは飽くまで翻訳記事です。直訳で意味のとりにくいものは文を補って意訳してあります(気になるひとは原文を読んでください)。ただし投稿者(@tadsan)の見解とは異なる点については、可能な限り私見を入れず、原文の意図を可能な限り汲みました。

変数

意味があって発音できる変数名にする

:no_good_tone1: Bad (だめ)

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

:ok_woman_tone1: Good (よい)

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

同じ型の変数には同じ単語を宛てる

:no_good_tone1: Bad (だめ)

getUserInfo();
getClientData();
getCustomerRecord();

:ok_woman_tone1: Good (よい)

getUser();

検索しやすい変数名にする

わたしたちはコードを書き続ける限り、書く以上に周囲のコードを読むことになります。なので、コードは読みやすく、検索しやすいように書くことが大事です。意味をつかみにくい変数名はプログラムを理解する上で支障となり、のちのち読者を困らせることになります。くれぐれも、変数には検索しやすい名前をつけてください。

:no_good_tone1: Bad (だめ)

// 86400って何…??
addExpireAt(86400);

:ok_woman_tone1: Good (よい)

// constキーワードで、全部大文字の定数名で定義する
interface DateGlobal {
    const SECONDS_IN_A_DAY = 86400;
}

addExpireAt(DateGlobal::SECONDS_IN_A_DAY);

説明的な変数名にする

:no_good_tone1: Bad: (だめ)

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

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

:thinking: Not bad: (悪くはない)

さっきより良いですが、まだ正規表現のマッチ順序に強く依存があります。

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

list(, $city, $zipCode) = $matchers;
saveCityZipCode($city, $zipCode);

:ok_woman_tone1: Good (よい)

名前付きサブパターンにすることでマッチ順序に影響されなくなったので、正規表現のへの依存が減りました。

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

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

メンタルマッピングを避ける

解読が必要になるような、独特の意味を持った変数名をつけないでください。変数の意味がはっきりと明確であることは、暗黙的であるよりも良いことです。

:no_good_tone1: Bad (だめ)

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

for ($i = 0; $i < count($l); $i++) {
    $li = $l[$i];
    doStuff();
    doSomeOtherStuff();
    // ...
    // ...
    // ...
    // また `$li` が出てきたけど… これは何?
    dispatch($li);
}

:ok_woman_tone1: Good (よい)

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

foreach ($locations as $location) {
    doStuff();
    doSomeOtherStuff();
    // ...
    // ...
    // ...
    dispatch($location);
});

名前にコンテキストをくっつけない

クラス/オブジェクトの名前に意味があるなら、それを変数名に繰り返さないでください。

:no_good_tone1: Bad (だめ)

class Car
{
    public $carMake;
    public $carModel;
    public $carColor;

    //...
}

:ok_woman_tone1: Good (よい)

class Car
{
    public $make;
    public $model;
    public $color;

    //...
}

短絡評価ではなく、デフォルト引数にする

:no_good_tone1: Bad (だめ)

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

:ok_woman_tone1: Good (よい)

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

関数

引数は2つ以下が理想

パラメータの数を制限することは、関数をテストしやすくするために重要です。3つ以上になると組み合せ爆発を引き起こします。そうなると、べつべつの値を渡して、いろいろなケースをテストしなければいけなくなります。

引数なしは理想的です。1つの場合と2つの場合は特に問題ありません。3つは避けてください。4個以上なら、それは連結するべきです。引数が2つ以上は、たいてい一つの関数に多くの処理を持ちすぎです。そうでなければ、高級なオブジェクトのメソッドはほとんどの場合に引数をひとつだけ取れば十分です。

:no_good_tone1: Bad (だめ)

function createMenu($title, $body, $buttonText, $cancellable) {
    // ...
}

:ok_woman_tone1: Good (よい)

class MenuConfig
{
    public $title;
    public $body;
    public $buttonText;
    public $cancellable = false;
}

$config = new MenuConfig();
$config->title = 'Foo';
$config->body = 'Bar';
$config->buttonText = 'Baz';
$config->cancellable = true;

function createMenu(MenuConfig $config) {
    // ...
}

関数はひとつのことだけをやる

これはソフトウェアエンジニアリングのなかでも、もっとも大事なルールです。関数が多くのことをしようとすると、実装、テスト、そして挙動を推測することは難しくなります。関数をひとつのアクションだけに隔離できればリファクタリングはずっと簡単になり、コードは洗練されます。

このガイドからほかの項目を削除したとしても、これだけは覚えておいてください。それだけで、ほかの大勢の開発者の一歩先を行くことができます。

:no_good_tone1: Bad (だめ)

function emailClients($clients) {
    foreach ($clients as $client) {
        $clientRecord = $db->find($client);
        if ($clientRecord->isActive()) {
            email($client);
        }
    }
}

:ok_woman_tone1: Good (よい)

function emailClients($clients) {
    $activeClients = activeClients($clients);
    array_walk($activeClients, 'email');
}

function activeClients($clients) {
    return array_filter($clients, 'isClientActive');
}

function isClientActive($client) {
    $clientRecord = $db->find($client);
    return $clientRecord->isActive();
}

何をするの関数なのかわかる名前にする

:no_good_tone1: Bad (だめ)

class Email
{
    //...

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

$message = new Email(...);
// メッセージのハンドルって、これは何やってるの…? ファイルに書き込むの?
$message->handle();

:ok_woman_tone1: Good (よい)

class Email 
{
    //...

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

$message = new Email(...);
// 送信する。一目瞭然。
$message->send();

関数の抽象化レベルを同じにする

抽象化のレベルが異なるなら、その関数は多くのことをやりすぎです。関数を分割することで再利用性が向上し、テストしやすくなります。

:no_good_tone1: Bad (だめ)

function parseBetterJSAlternative($code)
{
    $regexes = [
        // ...
    ];
    
    $statements = split(' ', $code);
    $tokens = [];
    foreach ($regexes as $regex) {
        foreach ($statements as $statement) {
            // ...
        }
    }
    
    $ast = [];
    foreach ($tokens as $token) {
        // 字句解析...
    }
    
    foreach ($ast as $node) {
        // 構文解析...
    }
}

:no_good_tone1::no_good_tone1: Bad too (まだだめ)

機能をいくつか外に出しましたが、それでもまだparseBetterJSAlternative()は複雑でテストしにくいです。

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

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

function parseBetterJSAlternative($code)
{
    $tokens = tokenize($code);
    $ast = lexer($tokens);
    foreach ($ast as $node) {
        // parse...
    }
}

:ok_woman_tone1: Good (よい)

最高の解決方法は、依存をparseBetterJSAlternative()から取り去ることです。

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

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

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

        return $ast;
    }
}
class BetterJSAlternative
{
    private $tokenizer;
    private $lexer;

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

    public function parse($code)
    {
        $tokens = $this->tokenizer->tokenize($code);
        $ast = $this->lexer->lexify($tokens);
        foreach ($ast as $node) {
            // parse...
        }
    }
}

私たちは依存をモックできるようになったので、BetterJSAlternative::parse()だけをテストできるようになりました。

フラグ引数は利用しない

引数にフラグを受け付けることは、関数が複数の処理を持つことを示します。関数はひとつの仕事だけに専念すべきです。フラグによって異なる処理をするならば、それは別の関数にしてください。

:no_good_tone1: Bad (だめ)

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

:ok_woman_tone1: Good (よい)

function createFile($name) {
    touch($name);
}

function createTempFile($name) {
    touch('./temp/'.$name);
}

副作用を避ける

ある関数が「引数を受け取って別の値を返す」以外の処理をするならば、それが副作用です。副作用とは、ファイル書き込み、グローバル変数の操作、そしてトラブルであなたの全財産をどこかの誰かに送金すること、などです。

ふつう、ひとつのプログラムにおいてもいくつもの副作用を持たせる必要があります。前の例のように、ファイルに書き込む必要があるのかもしれません。あなたがやりたいことは、それをするところを集約することです。そのファイルに書き込む関数やクラスを分散させないでください。ひとつのサービスにそれをさせます。唯一それだけです。

「構造体ではないオブジェクトの間で状態を共有する」「どこからも変更されるおそれがあるものにミュータブルなデータ型を利用する」、そして「副作用の起こる処理を一箇所に集約しない」といった誰もが陥る罠を避けることができれば、あなたは大多数のプログラマーよりも幸せになることができます。

:no_good_tone1: Bad (だめ)

// グローバル変数がこれらの関数から参照される
// もし同じ変数名を使用しようとする別の関数があったら、この処理は壊れます
$name = 'Ryan McDermott';

function splitIntoFirstAndLastName() {
    global $name;

    $name = preg_split('/ /', $name);
}

splitIntoFirstAndLastName();

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

:ok_woman_tone1: Good (よい)

$name = 'Ryan McDermott';

function splitIntoFirstAndLastName($name) {
    return preg_split('/ /', $name);
}

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

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

クローバル関数は書かない

あなたのライブラリと別のライブラリと関数名が衝突する可能性があるので、本番環境の例外を見るまであなたのライブラリのユーザーを混乱させることがあるかもしれません1。そのため、汚染されたグローバル名前空間は多くのプログラミング言語で悪い習慣とされます。

例として、コンフィグを配列として取得したいとします。config()のようなグローバル配列を定義することもできますが、別のライブラリも似たような目的で自分自身のコンフィグを取得させるためにconfig()を定義するかもしれません。

:no_good_tone1: Bad (だめ)

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

:ok_woman_tone1: Good (よい)

設定ファイルを.phpファイルとして作成します。

// config.php
return [
    'foo' => 'bar',
];
class Configuration
{
    private $configuration = [];

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

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

ファイルから設定をロードして、Configurationクラスのインスタンスを作成します。

$configuration = new Configuration($configuration);

アプリケーションではConfigurationのインスタンスを使用する必要があります。

シングルトンパターンは使用しない

Singleton パターンはアンチパターンです。

:no_good_tone1: Bad (だめ)

class DBConnection
{
    private static $instance;

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

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

        return self::$instance;
    }

    // ...
}

$singleton = DBConnection::getInstance();

:ok_woman_tone1: Good (よい)

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

     // ...
}

DBConnectionクラスのインスタンスを作成し、DSN文字列でそれを設定します。

$connection = new DBConnection($dsn);

アプリケーションではDBConnectionのインスタンスを使用する必要があります。

条件をカプセル化する

:no_good_tone1: Bad (だめ)

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

:ok_woman_tone1: Good (よい)

if ($article->isPublished()) {
    // ...
}

逆の条件を避ける

:no_good_tone1: Bad (だめ)

function isDOMNodeNotPresent($node) {
    // ...
}

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

:ok_woman_tone1: Good (よい)

function isDOMNodePresent($node) {
    // ...
}

if (isDOMNodePresent($node)) {
    // ...
}

条件文を避ける

そんなことは不可能なように見えます。条件文を避けると聞いたとき、ほとんどのひとは「if文なしで何ができるの?」と漏らします。その疑問への回答は「ほとんどの場合はポリモーフィズムで同じことができる」です。

多くのひとは続いて「それはすごいんだけどさ、そうすると何がどうなるの?」と尋ねます。それに対する私の回答は、この記事を通じて学んできた「関数はひとつのことだけをやる」です。覚えておいてください。「関数はひとつのことだけをやる」です。

:no_good_tone1: Bad (だめ)

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

:ok_woman_tone1: Good (よい)

class Airplane {
    // ...
}

class Boeing777 extends Airplane {
    // ...
    public function getCruisingAltitude() {
        return $this->getMaxAltitude() - $this->getPassengerCount();
    }
}

class AirForceOne extends Airplane {
    // ...
    public function getCruisingAltitude() {
        return $this->getMaxAltitude();
    }
}

class Cessna extends Airplane {
    // ...
    public function getCruisingAltitude() {
        return $this->getMaxAltitude() - $this->getFuelExpenditure();
    }
}

型チェックを避ける (その1)

PHPの関数には型がなく、どんな型の引数でも受け付けることができます。時にはこの自由に足を噛まれ、時に関数で型チェックをする誘惑に駆られます。しかし、これを避ける方法はいくつもあります。はじめに考慮すべきは、一貫したAPIです。

:no_good_tone1: Bad (だめ)

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

:ok_woman_tone1: Good (よい)

function travelToTexas(Traveler $vehicle)
{
    $vehicle->travelTo(new Location('texas'));
}

型チェックを避ける (その2)

文字列、整数、そして配列のような基本的なプリミティブ値を使って関数を定義するとき、もしあなたがPHP7以降で、そしてポリモーフィズムを採用することはできないが型チェックに頼りたくなったなら、型宣言と「厳密な型チェック」モードは一考の余地があります。これは通常のPHPの構文の上に静的型付けを追加します。

手作業での型チェックは、それをうまく動作させるためには余分な記述が必要になることが問題です。それによって得たまやかしの型安全性は、損ねられた読みにくさを補完できません。PHPのコードを綺麗に保ち、良いテストを書き、そしてきちんとコードレビューをしてください。そうでなければ、PHPの型宣言と「厳密な型チェック」モードを利用することでそれを実現できます。

:no_good_tone1: Bad (だめ)

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

    return $val1 + $val2;
}

:ok_woman_tone1: Good (よい)

function combine(int $val1, int $val2)
{
    return $val1 + $val2;
}

不要コードは消す

デッドコード(不要なコード)は重複コードと同じく悪です。理由もないのにそれを残さないでください。もし呼ばれないなら、消し去ってください! もし将来必要になるだろうと思っても、それはGitなどのバージョン管理システムの履歴から安全に取り出すことができます。

:no_good_tone1: Bad (だめ)

function oldRequestModule($url) {
    // ...
}

function newRequestModule($url) {
    // ...
}

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

:ok_woman_tone1: Good (よい)

function requestModule($url) {
    // ...
}

$req = new requestModule($requestUrl);
inventoryTracker('apples', $req, 'www.inventory-awesome.io');

いまのところ、翻訳はここまで。暇があったら、明日は後日には続きを訳します。


オブジェクトとデータ構造

ゲッターとセッターを利用する

PHPではメソッドにpublic, protected, privateのキーワードを指定できます。これを利用することでプロパティの変更を制御できます。

  • When you want to do more beyond getting an object property, you don't have to look up and change every accessor in your codebase.
  • Makes adding validation simple when doing a set.
  • Encapsulates the internal representation.
  • Easy to add logging and error handling when getting and setting.
  • Inheriting this class, you can override default functionality.
  • You can lazy load your object's properties, let's say getting it from a
    server.

Additionally, this is part of Open/Closed principle, from object-oriented
design principles.

:no_good_tone1: Bad (だめ)

class BankAccount {
    public $balance = 1000;
}

$bankAccount = new BankAccount();

// Buy shoes...
$bankAccount->balance -= 100;

:ok_woman_tone1: Good (よい)

class BankAccount {
    private $balance;
    
    public function __construct($balance = 1000) {
      $this->balance = $balance;
    }
    
    public function withdrawBalance($amount) {
        if ($amount > $this->balance) {
            throw new \Exception('Amount greater than available balance.');
        }
        $this->balance -= $amount;
    }
    
    public function depositBalance($amount) {
        $this->balance += $amount;
    }
    
    public function getBalance() {
        return $this->balance;
    }
}

$bankAccount = new BankAccount();

// Buy shoes...
$bankAccount->withdrawBalance($shoesPrice);

// Get balance
$balance = $bankAccount->getBalance();

オブジェクトのプロパティはprivate/protectedにする

:no_good_tone1: Bad (だめ)

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

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

:ok_woman_tone1: Good (よい)

class Employee {
    protected $name;
    
    public function __construct($name) {
        $this->name = $name;
    }
    
    public function getName() {
        return $this->name;
    }
}

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

クラス

S: 単一責任の原則 (SRP)

As stated in Clean Code, "There should never be more than one reason for a class
to change". It's tempting to jam-pack a class with a lot of functionality, like
when you can only take one suitcase on your flight. The issue with this is
that your class won't be conceptually cohesive and it will give it many reasons
to change. Minimizing the amount of times you need to change a class is important.
It's important because if too much functionality is in one class and you modify a piece of it,
it can be difficult to understand how that will affect other dependent modules in
your codebase.

:no_good_tone1: Bad (だめ)

class UserSettings {
    private $user;
    public function __construct($user) {
        $this->user = user;
    }
    
    public function changeSettings($settings) {
        if ($this->verifyCredentials()) {
            // ...
        }
    }
    
    private function verifyCredentials() {
        // ...
    }
}

:ok_woman_tone1: Good (よい)

class UserAuth {
    private $user;
    public function __construct($user) {
        $this->user = user;
    }
    
    public function verifyCredentials() {
        // ...
    }
}


class UserSettings {
    private $user;
    public function __construct($user) {
        $this->user = $user;
        $this->auth = new UserAuth($user);
    }
    
    public function changeSettings($settings) {
        if ($this->auth->verifyCredentials()) {
            // ...
        }
    }
}

O: オープン/クローズドの原則 (OCP)

As stated by Bertrand Meyer, "software entities (classes, modules, functions,
etc.) should be open for extension, but closed for modification." What does that
mean though? This principle basically states that you should allow users to
add new functionalities without changing existing code.

:no_good_tone1: Bad (だめ)

abstract class Adapter
{
    protected $name;

    public function getName()
    {
        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)
    {
        $this->adapter = $adapter;
    }
    
    public function fetch($url)
    {
        $adapterName = $this->adapter->getName();

        if ($adapterName === 'ajaxAdapter') {
            return $this->makeAjaxCall($url);
        } elseif ($adapterName === 'httpNodeAdapter') {
            return $this->makeHttpCall($url);
        }
    }
    
    protected function makeAjaxCall($url)
    {
        // request and return promise
    }
    
    protected function makeHttpCall($url)
    {
        // request and return promise
    }
}

:ok_woman_tone1: Good (よい)

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

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

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

class HttpRequester
{
    private $adapter;

    public function __construct(Adapter $adapter)
    {
        $this->adapter = $adapter;
    }
    
    public function fetch($url)
    {
        return $this->adapter->request($url);
    }
}

L: リスコフの置換原則 (LSP)

This is a scary term for a very simple concept. It's formally defined as "If S
is a subtype of T, then objects of type T may be replaced with objects of type S
(i.e., objects of type S may substitute objects of type T) without altering any
of the desirable properties of that program (correctness, task performed,
etc.)." That's an even scarier definition.

The best explanation for this is if you have a parent class and a child class,
then the base class and child class can be used interchangeably without getting
incorrect results. This might still be confusing, so let's take a look at the
classic Square-Rectangle example. Mathematically, a square is a rectangle, but
if you model it using the "is-a" relationship via inheritance, you quickly
get into trouble.

:no_good_tone1: Bad (だめ)

class Rectangle {
    private $width, $height;
    
    public function __construct() {
        $this->width = 0;
        $this->height = 0;
    }
    
    public function setColor($color) {
        // ...
    }
    
    public function render($area) {
        // ...
    }
    
    public function setWidth($width) {
        $this->width = $width;
    }
    
    public function setHeight($height) {
        $this->height = $height;
    }
    
    public function getArea() {
        return $this->width * $this->height;
    }
}

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

function renderLargeRectangles($rectangles) {
    foreach($rectangle in $rectangles) {
        $rectangle->setWidth(4);
        $rectangle->setHeight(5);
        $area = $rectangle->getArea(); // BAD: Will return 25 for Square. Should be 20.
        $rectangle->render($area);
    });
}

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

:ok_woman_tone1: Good (よい)

abstract class Shape {
    private $width, $height;
    
    abstract public function getArea();
    
    public function setColor($color) {
        // ...
    }
    
    public function render($area) {
        // ...
    }
}

class Rectangle extends Shape {
    public function __construct {
    parent::__construct();
        $this->width = 0;
        $this->height = 0;
    }
    
    public function setWidth($width) {
        $this->width = $width;
    }
    
    public function setHeight($height) {
        $this->height = $height;
    }
    
    public function getArea() {
        return $this->width * $this->height;
    }
}

class Square extends Shape {
    public function __construct {
        parent::__construct();
        $this->length = 0;
    }
    
    public function setLength($length) {
        $this->length = $length;
    }
    
    public function getArea() {
        return pow($this->length, 2);
    }
}

function renderLargeRectangles($rectangles) {
    foreach($rectangle in $rectangles) {
        if ($rectangle instanceof Square) {
            $rectangle->setLength(5);
        } else if ($rectangle instanceof Rectangle) {
            $rectangle->setWidth(4);
            $rectangle->setHeight(5);
        }
        
        $area = $rectangle->getArea(); 
        $rectangle->render($area);
    });
}

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

I: インターフェイス分離の原則 (ISP)

ISP states that "Clients should not be forced to depend upon interfaces that
they do not use."

A good example to look at that demonstrates this principle is for
classes that require large settings objects. Not requiring clients to setup
huge amounts of options is beneficial, because most of the time they won't need
all of the settings. Making them optional helps prevent having a "fat interface".

:no_good_tone1: Bad (だめ)

interface WorkerInterface {
    public function work();
    public function eat();
}

class Worker implements WorkerInterface {
    public function work() {
        // ....working
    }
    public function eat() {
        // ...... eating in launch break
    }
}

class SuperWorker implements WorkerInterface {
    public function work() {
        //.... working much more
    }

    public function eat() {
        //.... eating in launch break
    }
}

class Manager {
  /** @var WorkerInterface $worker **/
  private $worker;
  
  public function setWorker(WorkerInterface $worker) {
        $this->worker = $worker;
    }

    public function manage() {
        $this->worker->work();
    }
}

:ok_woman_tone1: Good (よい)

interface WorkerInterface extends FeedableInterface, WorkableInterface {
}

interface WorkableInterface {
    public function work();
}

interface FeedableInterface {
    public function eat();
}

class Worker implements WorkableInterface, FeedableInterface {
    public function work() {
        // ....working
    }

    public function eat() {
        //.... eating in launch break
    }
}

class Robot implements WorkableInterface {
    public function work() {
        // ....working
    }
}

class SuperWorker implements WorkerInterface  {
    public function work() {
        //.... working much more
    }

    public function eat() {
        //.... eating in launch break
    }
}

class Manager {
  /** @var $worker WorkableInterface **/
    private $worker;

    public function setWorker(WorkableInterface $w) {
      $this->worker = $w;
    }

    public function manage() {
        $this->worker->work();
    }
}

D: 依存逆転の法則 (DIP)

This principle states two essential things:

  1. High-level modules should not depend on low-level modules. Both should
    depend on abstractions.
  2. Abstractions should not depend upon details. Details should depend on
    abstractions.

This can be hard to understand at first, but if you've worked with PHP frameworks (like Symfony), you've seen an implementation of this principle in the form of Dependency
Injection (DI). While they are not identical concepts, DIP keeps high-level
modules from knowing the details of its low-level modules and setting them up.
It can accomplish this through DI. A huge benefit of this is that it reduces
the coupling between modules. Coupling is a very bad development pattern because
it makes your code hard to refactor.

:no_good_tone1: Bad (だめ)

class Worker {
  public function work() {
    // ....working
  }
}

class Manager {
    /** @var Worker $worker **/
    private $worker;
    
    public function __construct(Worker $worker) {
        $this->worker = $worker;
    }
    
    public function manage() {
        $this->worker->work();
    }
}

class SuperWorker extends Worker {
    public function work() {
        //.... working much more
    }
}

:ok_woman_tone1: Good (よい)

interface WorkerInterface {
    public function work();
}

class Worker implements WorkerInterface {
    public function work() {
        // ....working
    }
}

class SuperWorker implements WorkerInterface {
    public function work() {
        //.... working much more
    }
}

class Manager {
    /** @var Worker $worker **/
    private $worker;
    
    public function __construct(WorkerInterface $worker) {
        $this->worker = $worker;
    }
    
    public function manage() {
        $this->worker->work();
    }
}

メソッドチェーンを利用する

This pattern is very useful and commonly used it many libraries such
as PHPUnit and Doctrine. It allows your code to be expressive, and less verbose.
For that reason, I say, use method chaining and take a look at how clean your code
will be. In your class functions, simply return this at the end of every function,
and you can chain further class methods onto it.

:no_good_tone1: Bad (だめ)

class Car {
    private $make, $model, $color;
    
    public function __construct() {
        $this->make = 'Honda';
        $this->model = 'Accord';
        $this->color = 'white';
    }
    
    public function setMake($make) {
        $this->make = $make;
    }
    
    public function setModel($model) {
        $this->model = $model;
    }
    
    public function setColor($color) {
        $this->color = $color;
    }
    
    public function dump() {
        var_dump($this->make, $this->model, $this->color);
    }
}

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

:ok_woman_tone1: Good (よい)

class Car {
    private $make, $model, $color;
    
    public function __construct() {
        $this->make = 'Honda';
        $this->model = 'Accord';
        $this->color = 'white';
    }
    
    public function setMake($make) {
        $this->make = $make;
        
        // NOTE: Returning this for chaining
        return $this;
    }
    
    public function setModel($model) {
        $this->model = $model;
        
        // NOTE: Returning this for chaining
        return $this;
    }
    
    public function setColor($color) {
        $this->color = $color;
        
        // NOTE: Returning this for chaining
        return $this;
    }
    
    public function dump() {
        var_dump($this->make, $this->model, $this->color);
    }
}

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

継承よりもコンポジション

As stated famously in Design Patterns by the Gang of Four,
you should prefer composition over inheritance where you can. There are lots of
good reasons to use inheritance and lots of good reasons to use composition.
The main point for this maxim is that if your mind instinctively goes for
inheritance, try to think if composition could model your problem better. In some
cases it can.

You might be wondering then, "when should I use inheritance?" It
depends on your problem at hand, but this is a decent list of when inheritance
makes more sense than composition:

  1. Your inheritance represents an "is-a" relationship and not a "has-a"
    relationship (Human->Animal vs. User->UserDetails).
  2. You can reuse code from the base classes (Humans can move like all animals).
  3. You want to make global changes to derived classes by changing a base class.
    (Change the caloric expenditure of all animals when they move).

:no_good_tone1: Bad (だめ)

class Employee {
    private $name, $email;
    
    public function __construct($name, $email) {
        $this->name = $name;
        $this->email = $email;
    }
    
    // ...
}

// Bad because Employees "have" tax data. 
// EmployeeTaxData is not a type of Employee

class EmployeeTaxData extends Employee {
    private $ssn, $salary;
    
    public function __construct($name, $email, $ssn, $salary) {
        parent::__construct($name, $email);
        $this->ssn = $ssn;
        $this->salary = $salary;
    }
    
    // ...
}

:ok_woman_tone1: Good (よい)

class EmployeeTaxData {
    private $ssn, $salary;
    
    public function __construct($ssn, $salary) {
        $this->ssn = $ssn;
        $this->salary = $salary;
    }
    
    // ...
}

class Employee {
    private $name, $email, $taxData;
    
    public function __construct($name, $email) {
        $this->name = $name;
        $this->email = $email;
    }
    
    public function setTaxData($ssn, $salary) {
        $this->taxData = new EmployeeTaxData($ssn, $salary);
    }
    // ...
}

同じことを繰り返すな (DRY)

Try to observe the DRY principle.

Do your absolute best to avoid duplicate code. Duplicate code is bad because
it means that there's more than one place to alter something if you need to
change some logic.

Imagine if you run a restaurant and you keep track of your inventory: all your
tomatoes, onions, garlic, spices, etc. If you have multiple lists that
you keep this on, then all have to be updated when you serve a dish with
tomatoes in them. If you only have one list, there's only one place to update!

Oftentimes you have duplicate code because you have two or more slightly
different things, that share a lot in common, but their differences force you
to have two or more separate functions that do much of the same things. Removing
duplicate code means creating an abstraction that can handle this set of different
things with just one function/module/class.

Getting the abstraction right is critical, that's why you should follow the
SOLID principles laid out in the Classes section. Bad abstractions can be
worse than duplicate code, so be careful! Having said this, if you can make
a good abstraction, do it! Don't repeat yourself, otherwise you'll find yourself
updating multiple places anytime you want to change one thing.

:no_good_tone1: Bad (だめ)

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

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

:ok_woman_tone1: Good (よい)

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

Very good:

It is better to use a compact version of the code.

function showList($employees)
{
    foreach ($employees as $employee) {
        render([
            $employee->calculateExpectedSalary(),
            $employee->getExperience(),
            $employee->getGithubLink()
        ]);
    }
}

脚注

  1. 訳注: 本当かよ…? (翻訳に自信が持てない) 原文では以下の通り: Polluting globals is a bad practice in very languages because you could clash with another library and the user of your API would be none-the-wiser until they get an exception in production.

216
221
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
216
221

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?