延期:Go to PHP

Goには便利な延期構造があります。 通常、リソースを解放するために使用され、次のように機能します。延期引数として、関数のリストに配置された関数が渡されます。 この関数のリストは、囲んでいる関数を終了すると実行されます。







据え置きには、いくつかの明らかな点とあまり良い点はありません:









たとえば、次のようなコード:







class Utils { public function copyFile(string $sourceName, string $destName): void { $readHandle = fopen($sourceName, "r"); if ($readHandle === false) { throw new \Exception(); } $writeHandle = fopen($destName, "w"); if ($writeHandle === false) { fclose($readHandle); throw new \Exception(); } while (($buffer = fgets($readHandle)) !== false) { $wasFailure = fwrite($writeHandle, $buffer); if ($wasFailure) { fclose($readHandle); fclose($writeHandle); throw new \Exception(); } } if (!feof($readHandle)) { fclose($readHandle); fclose($writeHandle); throw new \Exception(); } fclose($readHandle); fclose($writeHandle); } }
      
      





これは次のようになります。







 class Utils { public function copyFile(string $sourceName, string $destName): void { $readHandle = fopen($sourceName, "r"); if ($readHandle === false) { throw new \Exception(); } defer fclose($readHandle); $writeHandle = fopen($destName, "w"); if ($writeHandle === false) { throw new \Exception(); } defer fclose($writeHandle); while (($buffer = fgets($readHandle)) !== false) { $wasFailure = fwrite($writeHandle, $buffer); if ($wasFailure) { throw new \Exception(); } } if (!feof($readHandle)) { throw new \Exception(); } } }
      
      





2番目のケースでは、閉じているファイルの操作がはるかに簡単です。各ファイルを閉じる必要があるのは1回だけです。 特に2つではなくそれ以上の場合、誰かがファイルを閉じるのを忘れる可能性を減らします。







しかし、残念ながらPHPには延期はありません。 ただし、独自の実装を作成できます。 次のようになります。







 class DeferredContext { protected $deferredActions = []; public function defer(callable $deferAction) { $this->deferredActions[] = $deferAction; } public function executeDeferredActions() { $actionsCount = count($this->deferredActions); if ($actionsCount > 0) { for ($i = $actionsCount - 1; $i >= 0; $i--) { $action = $this->deferredActions[$i]; try { $action(); } catch (\Exception $e) { } unset($this->deferredActions[$i]); } } $this->deferredActions = []; } } trait DeferredTrait { private function deferred(callable $callback) { $context = new DeferredContext(); try { $callback($context); } finally { $context->executeDeferredActions(); } } }
      
      





DeferredContext-ハンドラー関数が蓄積されるクラス。 関数を終了するときは、executeDeferredActions()メソッドを呼び出して、すべてのハンドラーを実行する必要があります。 DeferredContextを手動で作成しないようにするには、DeferredContextを操作するロジックをカプセル化するDeferredTraitトレイトを使用できます。







このアプローチを使用すると、上記の例のコードは次のようになります。







 class Utils { use DeferredTrait; public function copyFile(string $sourceName, string $destName): void { $this->deferred(function(DeferredContext $context) use ($destName, $sourceName) { $readHandle = fopen($sourceName, "r"); if ($readHandle === false) { throw new \Exception(); } $context->defer(function() use ($readHandle) { fclose($readHandle); }); $writeHandle = fopen($destName, "w"); if ($writeHandle === false) { throw new \Exception(); } $context->defer(function() use ($writeHandle) { fclose($writeHandle); }); while (($buffer = fgets($readHandle)) !== false) { $wasFailure = fwrite($writeHandle, $buffer); if ($wasFailure) { throw new \Exception(); } } if (!feof($readHandle)) { throw new \Exception(); } }); } }
      
      





このアイデアが、コードのバグの数を減らし、より信頼性の高いプログラムを作成するのに役立つことを願っています。








All Articles