関数


Function

1. 関数パラメータ (できれば 2 未満)

2. 関数は 1 つのことだけを実行する必要があります

3. 関数名はその機能を反映する必要があります

4. 関数内の抽象化層は 1 つだけである必要があります

5. 関数パラメータとしてフラグを使用しないでください

6. 副作用を回避してください

7.グローバル関数を記述しないでください

8. シングルトン モードを使用しないでください

9. 条件付きステートメントをカプセル化します

10. 反意語の条件判断の使用を避ける

11. 条件判断の使用を避ける

12. 型チェックを避ける (パート 1)

13. 型チェックを避ける (パート 2)

14. ゾンビ コードを削除する

1. 関数パラメーター (できれば 2 つ未満)

関数のテストを容易にするために、関数パラメーターの数を制限することが非常に重要です。オプションのパラメーターが 3 つを超えると、組み合わせが爆発的に増加し、テストすべき独立したパラメーターのシナリオが大量に存在することになります。

パラメータがないのが理想的な状況です。 1 または 2 は問題ありませんが、3 は避けた方がよいでしょう。それ以上になると補強が必要になります。通常、関数に 3 つ以上のパラメーターがある場合は、処理するには多すぎることを意味します。大量のデータを渡す必要がある場合は、高レベルのオブジェクトをパラメータとしてカプセル化することをお勧めします。

悪い:

 function createMenu(string $title, string $body, string $buttonText, bool $cancellable): void
{
    // ...
}

良い:

 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): void
{
    // ...
}

2. 関数は次のことのみを行う必要があります。一つのことを実行する

これは、ソフトウェア エンジニアリングにおいて最も重要なルールです。関数が複数のことを行う場合、実装、テスト、理解することが難しくなります。関数を 1 つの関数に分割すると、リファクタリングが容易になり、コードが読みやすくなります。このルールに従うだけで、ほとんどの開発者よりも先を行くことができます。

Bad:

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

Good:

 function emailClients(array $clients): void
{
    $activeClients = activeClients($clients);
    array_walk($activeClients, 'email');
}
 
function activeClients(array $clients): array
{
    return array_filter($clients, 'isClientActive');
}
 
function isClientActive(int $client): bool
{
    $clientRecord = $db->find($client);
 
    return $clientRecord->isActive();
}

3. 関数名彼がやったことを反映する必要があります

#悪い:
 class Email
{
    //...
 
    public function handle(): void
    {
        mail($this->to, $this->subject, $this->body);
    }
}
 
$message = new Email(...);
// 啥?handle处理一个消息干嘛了?是往一个文件里写吗?
$message->handle();

#良い:

 class Email
{
    //...
 
    public function send(): void
    {
        mail($this->to, $this->subject, $this->body);
    }
}
 
$message = new Email(...);
// 简单明了
$message->send();

4. 関数内の抽象化層は 1 つだけである必要があります。抽象化レベルが多すぎると、関数が処理する処理が多すぎます。分割機能は、テストを簡素化するために再利用性と使いやすさを向上させるために必要です。 (翻訳者注: ここのサンプル コードから判断すると、ネストが多すぎると思われます)

Bad:

 function parseBetterJSAlternative(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...
    }
}

Bad:

ループからいくつかのメソッドを抽出しましたが、

parseBetterJSAlternative()

メソッドは依然として非常に複雑で、テストには役立ちません。

 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 parseBetterJSAlternative(string $code): void
{
    $tokens = tokenize($code);
    $ast = lexer($tokens);
    foreach ($ast as $node) {
        // 解析逻辑...
    }
}

Good:

最良の解決策は、

parseBetterJSAlternative()

メソッドの依存関係を削除することです。

 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 BetterJSAlternative
{
    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) {
            // 解析逻辑...
        }
    }
}

これにより、依存関係を モックして、BetterJSAlternative::parse() が期待どおりに実行されるかどうかをテストできます。

5. flag を関数パラメータとして使用しないでください

flag は、このメソッドが多くのことを処理することを皆に伝えています。前述したように、関数は 1 つのことだけを実行する必要があります。さまざまなフラグのコードを複数の関数に分割します。

Bad:

 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);
}

副作用を避ける

##関数は、1 つの値を取得して別の値を返すだけではありません。 1 つまたは複数の値に副作用がある場合。副作用としては、ファイルへの書き込み、一部のグローバル変数の変更、または誤って見知らぬ人に全財産を渡してしまうことなどが考えられます。

ここで、プログラムまたは状況に副作用を持たせる必要があるため、前の例と同様に、ファイルを書き込む必要がある場合があります。あなたがやりたいのは、これを行う場所を一元化することです。特定のファイルに書き込むために複数の関数やクラスを使用しないでください。サービスとオンリーワンで実行してください。

オブジェクト間で非構造化データを共有すること、任意の型に書き込むことができる可変データ型を使用すること、副作用が発生する場所に焦点を当てないことなど、よくある落とし穴を回避することに重点を置いています。これを実行すれば、あなたはほとんどのプログラマよりも幸せになるでしょう。

悪い:

 // Global variable referenced by following function.
// If we had another function that used this name, now it'd be an array and it could break it.
$name = 'Ryan McDermott';
 
function splitIntoFirstAndLastName(): void
{
    global $name;
 
    $name = explode(' ', $name);
}
 
splitIntoFirstAndLastName();
 
var_dump($name); // ['Ryan', 'McDermott'];

良い:

 function splitIntoFirstAndLastName(string $name): array
{
    return explode(' ', $name);
}
 
$name = 'Ryan McDermott';
$newName = splitIntoFirstAndLastName($name);
 
var_dump($name); // 'Ryan McDermott';
var_dump($newName); // ['Ryan', 'McDermott'];

6. やめてくださいwrite global Function

グローバル変数を汚染することは、他のライブラリと競合する可能性があり、API を呼び出す人は、問題が発生するまで、問題が発生していることに気づかないため、ほとんどの言語では悪い習慣です。例外。シナリオについて考えてみましょう。配列を構成したい場合は、グローバル関数

config() を作成しますが、同じことを行おうとする他のライブラリと競合する可能性があります。

悪い:

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

良い:

class Configuration
{
    private $configuration = [];
 
    public function __construct(array $configuration)
    {
        $this->configuration = $configuration;
    }
 
    public function get(string $key): ?string
    {
        return isset($this->configuration[$key]) ? $this->configuration[$key] : null;
    }
}
加载配置并创建 Configuration 类的实例
 
$configuration = new Configuration([
    'foo' => 'bar',
]);

次に、プログラムで

Configuration のインスタンスを使用する必要があります

7. シングルトン パターンを使用しないでください

シングルトンはアンチパターンです。説明は次のとおりです: ブライアン バトンの意訳:

  1. は常にグローバル インスタンスとして使用されます。これらは通常、グローバル インスタンスとして使用されますが、なぜそれがそれほど悪いのでしょうか? アプリケーションの依存関係をインターフェイスを通じて公開するのではなく、コード内に隠すためです。渡さないように何かをグローバルにするのは、コードの匂いです。

  2. これらは、独自の作成とライフサイクルを制御するという事実により、単一責任の原則に違反します。

  3. コードへの誘導強い結合それらは本質的に原因を引き起こしますこのため、多くの場合、テスト中にコードを偽装することがかなり困難になります。

  4. プログラムの存続期間中、常に状態を保持します。アプリケーションの存続期間中、状態が保持されます。単体テストでは、テストを順序付ける必要があるという状況が発生する可能性があるため、テストにもう 1 つの問題が発生します。これは、単体テストにとって大きな問題です。なぜでしょうか。各単体テストは他の単体テストから独立している必要があるためです。 .

  5. ここでは、[シングルトン パターンの基本的な問題 ((http://misko.hevery.com/2008/08/25/root-cause-of-singletons/ ) この記事は Misko Hevery によって書かれました。

悪い:

 class DBConnection
{
    private static $instance;
 
    private function __construct(string $dsn)
    {
        // ...
    }
 
    public static function getInstance(): DBConnection
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
 
        return self::$instance;
    }
 
    // ...
}
 $singleton = DBConnection::getInstance();

良い:

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

DBConnection クラスのインスタンスを作成し、DSN 経由で設定します。

$connection = new DBConnection($dsn);

これで、 DBConnection

8 のインスタンスを使用する必要があります。条件文をカプセル化します。

Bad:

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

Good:

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

9.

を判断するために反意語条件を使用することは避けてください。 Bad:

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

Good:

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

10. 条件判断を避ける

これは不可能なタスクのように思えます。この文を最初に聞いたとき、人々はそう言います。「if ステートメントなしで何ができるでしょうか?」答えは、ポリモーフィズムを使用して、複数のシナリオで同じタスクを達成できるということです。2 番目の質問は次のとおりです。 「これを実行するのは問題ありませんが、なぜこれを実行する必要があるのですか?」 答えは、前に学んだクリーン コードの原則です: 関数は 1 つのことだけを実行する必要があります。 大量の if ステートメントがある場合 クラスと関数を使用する場合、関数は複数のことを行います。実行することは 1 つだけにしてください。

悪い例:

 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();
        }
    }
}

良い例:

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();
    }
}

11. 型チェックの回避 (パート 1)

PHP は弱い型付けであるため、関数はあらゆる型の引数を受け取ることができます。この自由を利用して、関数での型チェックを徐々に試してください。これを回避する方法はたくさんあります。1 つ目は、統合 API です。

#悪い:

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

Good:

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

##12. 型チェックの回避 (パート 2)

次のような基本的なプリミティブ値を使用している場合文字列、整数、および配列、バージョン PHP 7 が必要、ポリモーフィズムを使用しない、および型検出が必要な場合は、型宣言または厳密モードを考慮する必要があります。標準の PHP 構文に基づいた静的型付けが提供されます。問題の型を手動で確認するには多くの作業が必要ですあたかも安全のために可読性の損失を無視できるかのように、ナンセンスな内容です。 PHP をクリーンな状態に保ち、テストを作成し、コード レビューを行います。それが不可能な場合は、安全性を確保するために PHP の strict 型宣言と strict モードを使用してください。

悪い:

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

良い:

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

13. ゾンビ コードを削除する

ゾンビ コードは、重複コードと同じくらい悪質です。コードベースにそれを保持する理由はありません。一度も呼び出されていない場合は、削除してください。まだコードリポジトリにあるので安全です。

悪い:

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

良い:

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