制御フローの抽象化

プログラマーは、このように自分の作品を見ていなくても、常に抽象化の構築に携わっています。 ほとんどの場合、コンピューティング(関数の記述)または動作(手順とクラス)を抽象化しますが、これらの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ます。また、 retryhttp_retryretry役立つと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真剣に、表現「制御フロー」のより良い翻訳を考え出す必要があります。

Source: https://habr.com/ru/post/J157265/


All Articles