制御フローの抽象化

プログラマーは、このように自分の作品を見ていなくても、常に抽象化の構築に携わっています。 ほとんどの場合、コンピューティング(関数の記述)または動作(手順とクラス)を抽象化しますが、これらの2つのケースに加えて、作業では特にエラーの処理、リソースの管理、標準ハンドラーの作成、最適化の際に多くの繰り返しパターンがあります。



海外の友人が言うように、制御フローまたは「制御フロー」を抽象化するとはどういう意味ですか? 誰も誇示していない場合、制御構造はフローに関与しています。 これらの制御構造では不十分な場合があり、プログラムの目的の動作を抽象化する独自の機能を追加します。 Lisp、Ruby、Perlなどの言語では簡単ですが、他の言語では、たとえば高階関数を使用することもできます。



抽象化



最初から始めましょう。 新しい抽象化を構築するには何をする必要がありますか?

  1. 機能または動作の一部を強調表示します。
  2. 彼に名前を付けてください。
  3. それを実装します。
  4. 選択した名前の背後にある実装を非表示にします。


3番目の点は常に実行可能であるとは限りません。 実装する能力は、言語の柔軟性と抽象化に大きく依存します。



あなたの言語が十分に柔軟でない場合はどうなりますか? 実装する代わりに、テクニックを詳細に説明し、それを人気にして、新しい「設計パターン」を生み出すことができます。 または、パターニングがあなたにアピールしない場合は、より強力な言語に切り替えてください。



しかし、十分な理論、ビジネスに取りかかりましょう...



人生の例



通常のPythonコード(最小限の変更を加えた実際のプロジェクトから取得):



urls = ... photos = [] for url in urls: for attempt in range(DOWNLOAD_TRIES): try: photos.append(download_image(url)) break except ImageTooSmall: pass #     except (urllib2.URLError, httplib.BadStatusLine, socket.error), e: if attempt + 1 == DOWNLOAD_TRIES: raise
      
      





このコードには多くの側面があります:URLリストの繰り返し、画像のアップロード、写真内のアップロードされた画像の収集、小さな画像のスキップ、ネットワークエラーが発生した場合のアップロードの再試行。 これらの側面はすべて単一のコードに絡み合っていますが、それらの多くは、それらを分離することができれば、それ自体で有用です。



特に、繰り返し+結果のコレクションは、組み込みのmap



関数で実装されます。



 photos = map(download_image, urls)
      
      





他の側面もキャッチしてみましょう。 小さな写真をスキップすることから始めましょう。これは次のようになります。



 @contextmanager def skip(error): try: yield except error: pass for url in urls: with skip(ImageTooSmall): photos.append(download_image(url))
      
      





悪くはありませんが、欠点があります-mapの使用を放棄しなければなりませんでした。 とりあえずこの問題を残して、ネットワークエラートレランスの側面に移りましょう。 前の抽象化と同様に、次のように書くことができます。



 with retry(DOWNLOAD_TRIES, (urllib2.URLError, httplib.BadStatusLine, socket.error)): # ... do stuff
      
      





これだけは機能しません。Pythonでは、コードブロックを複数回実行することはできません。 私たちは言語の限界につまずき、今では代替ソリューションを最小限に抑えて使用するか、別の「パターン」を生み出すことを余儀なくされています。 言語の違いと、すべてがチューリング完全であるという事実にもかかわらず、一方が他方よりも強力になる方法を理解する場合は、このような状況に注意することが重要です。 ルビーでは、perlではあまり便利ではなく、ブロックを操作し続け、Lispでは-ブロックまたはコード(この場合、後者は明らかにゼロになります)、Pythonでは代替オプションを使用する必要があります。



より高次の関数、より正確にはそれらの特別な種類-デコレーターに戻りましょう:



 @decorator def retry(call, tries, errors=Exception): for attempt in range(tries): try: return call() except errors: if attempt + 1 == tries: raise http_retry = retry(DOWNLOAD_TRIES, (urllib2.URLError, httplib.BadStatusLine, socket.error)) harder_download_image = http_retry(download_image) photos = map(harder_download_image, urls)
      
      





ご覧のとおり、このアプローチはmap



の使用にhttp_retry



ます。また、 retry



http_retry



retry



役立つとhttp_retry



れることもいくつかありretry







skip



を同じスタイルで書き直します:



 @decorator def skip(call, errors=Exception): try: return call() except errors: return None skip_small = skip(ImageTooSmall) http_retry = retry(DOWNLOAD_TRIES, (urllib2.URLError, httplib.BadStatusLine, socket.error)) download = http_retry(skip_small(download_image)) photos = filter(None, map(download, urls))
      
      





破棄された画像をスキップfilter



必要でした。 実際、 filter(None, map(f, seq))



パターンfilter(None, map(f, seq))



非常に一般的であるため、一部の言語では、 この場合の組み込み関数があります。



これも実装できます。



 def keep(f, seq): return filter(None, map(f, seq)) photos = keep(download, urls)
      
      





結果は何ですか? これで、コードのすべての側面が見え、簡単に区別可能、変更可能、交換可能、および取り外し可能になりました。 また、ボーナスとして、将来使用できる一連の抽象化があります。 また、コードを改善する新しい方法を誰かに見てもらいたいと思います。



PS @decorator



の実装はここ@decorator



できます



PPS制御フローの抽象化の他の例: underscore.js内の関数の操作 、リストおよびジェネレーター式、 関数のオーバーロード関数のキャッシュラッパーなど。



PPPS真剣に、表現「制御フロー」のより良い翻訳を考え出す必要があります。



All Articles