プログラマーは、このように自分の作品を見ていなくても、常に抽象化の構築に携わっています。 ほとんどの場合、コンピューティング(関数の記述)または動作(手順とクラス)を抽象化しますが、これらの2つのケースに加えて、作業では特にエラーの処理、リソースの管理、標準ハンドラーの作成、最適化の際に多くの繰り返しパターンがあります。
海外の友人が言うように、制御フローまたは「制御フロー」を抽象化するとはどういう意味ですか? 誰も誇示していない場合、制御構造はフローに関与しています。 これらの制御構造では不十分な場合があり、プログラムの目的の動作を抽象化する独自の機能を追加します。 Lisp、Ruby、Perlなどの言語では簡単ですが、他の言語では、たとえば高階関数を使用することもできます。
抽象化
最初から始めましょう。 新しい抽象化を構築するには何をする必要がありますか?
- 機能または動作の一部を強調表示します。
- 彼に名前を付けてください。
- それを実装します。
- 選択した名前の背後にある実装を非表示にします。
3番目の点は常に実行可能であるとは限りません。 実装する能力は、言語の柔軟性と抽象化に大きく依存します。
あなたの言語が十分に柔軟でない場合はどうなりますか? 実装する代わりに、テクニックを詳細に説明し、それを人気にして、新しい「設計パターン」を生み出すことができます。 または、パターニングがあなたにアピールしない場合は、より強力な言語に切り替えてください。
しかし、十分な理論、ビジネスに取りかかりましょう...
人生の例
通常のPythonコード(最小限の変更を加えた実際のプロジェクトから取得):
urls = ... photos = [] for url in urls: for attempt in range(DOWNLOAD_TRIES): try: photos.append(download_image(url)) break except ImageTooSmall: pass
このコードには多くの側面があります: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)):
これだけは機能しません。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真剣に、表現「制御フロー」のより良い翻訳を考え出す必要があります。