首页 存档 技术 查看内容

控制流的抽象 控制流的抽象

2018-3-30 13:00 |来自: 互联网 265 0

摘要: 控制流的抽象 本文翻译自Abstracting Control Flow。 所有的程序员都在持续不断地创造抽象,尽管有时候连他们自己也意识不到。我们平常最常抽象的是运算(写成函数)或者行为(子程序或者类),但其实我们的工作中 ...

控制流的抽象


本文翻译自Abstracting Control Flow



所有的程序员都在持续不断地创造抽象,尽管有时候连他们自己也意识不到。我们平常最常抽象的是运算(写成函数)或者行为(子程序或者类),但其实我们的工作中还有一些其他的重复的模式,特别是异常处理、资源管理与优化。


这些重复的模式通常会引入一些规则,例如「关闭所有你打开的东西」,「释放资源然后抛出异常」,「如果成功了则继续,否则……」,这些代码通常都是重复的if ... else或者try ... catch。不如把这些控制流也抽象出来?


在那些没人秀技巧的常规代码里,通常使用控制结构来控制流程。但有时候它们并不能完成得太好,于是我们就只能靠自己动手了。这在 Lisp、Ruby 或者 Perl 里面很容易做到,在所有支持高阶函数的语言中也有办法可以做到。




抽象


让我们从头开始吧。创建一个新的抽象我们需要做些什么呢?

  1. 选择一个功能或者行为。

  2. 给它命名。

  3. 实现它。

  4. 把我们的实现细节隐藏在这个命名之后。


第三点和第四点不一定总是可以做到的。这与你试图抽象的东西和你所使用的语言的灵活性有非常大的关系。


如果你所使用的语言做不到这些,那就忽略实现这一步,仅仅描述实现的方法就好了,然后想办法让它流行起来,从而创造一个新的设计模式。这样你就不会对你将要写的那些重复代码感到糟糕了。




回到现实


这是一段普通的 Python 代码,它是从真实的项目中拿出来的,只做了很少的修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
urls = ...
photos = []

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

这段代码实现了好几个功能:迭代urls、下载图片、把图片收集到photos里、忽略小图片和在下载失败时重试。这一大堆功能全都塞进了这段单独的代码中,尽管它们在这段代码之外也可能被用到。


这其中的有些功能其实已经存在了。比如,迭代后的结果聚合其实就是map

1
photos = map(download_image, urls)

让我们来实现其他的功能。先从忽略小图片开始,它可以写成:

1
2
3
4
5
6
7
8
9
10
11
@contextmanager
def ignore(error):
try:
yield
except error:
pass

photos = []
for url in urls:
with ignore(ImageTooSmall):
photos.append(download_image(url))

看起来不错的样子。但这样写不能轻易地与map组合起来使用。让我们暂时忽略它,先来解决网络错误问题。我们可以试着用我们刚刚处理ignore的方法来抽象它:

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

只有这个是不能被正确实现的。Python 的with语句不能重复地运行它包含的语句块。我们碰到了语言的**。如果你想要在语法层面之上理解不同语言之间的差别,那么注意到这样类似的情况是很重要的。在 Ruby 和 Perl 的小扩展版本中,我们可以继续操作语句块,在 Lisp 中我们甚至可以操作代码(这可能有点杀伤力过猛了),但是这些特性在 Python 中全失去了,我们应该转而使用高阶函数和它们的简化形式装饰器:

1
2
3
4
5
6
7
8
9
10
11
@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))
photos = map(http_retry(download_image), urls)

我们可以看到,它甚至自然地就可以和map同时工作了。另外,我们还得到了一对非常有用的可复用的工具:retryhttp_retry。不幸的是我们的ignorecontextmanager 不能轻松地在这里添加。它是不可被组合的。让我们来把它也重写成装饰器:

1
2
3
4
5
6
7
8
9
10
11
@decorator
def ignore(call, errors=Exception):
try:
return call()
except errors:
return None

ignore_small = ignore(ImageTooSmall)
http_retry = retry(DOWNLOAD_TRIES, (urllib2.URLError, httplib.BadStatusLine, socket.error))
download = http_retry(ignore_small(download_image))
photos = filter(None, map(download, urls))

这么做好在哪儿?


似乎我们现在为实现同等的功能写了更多的代码。不同的地方就在于这些功能现在没有混乱地耦合在一起了,并且它们是组合起来的,这意味着这几件事情:

  • 每个单独的功能都是可见的,

  • 它被命名了,

  • 它可以很容易地被使用和输出,

  • 它是可复用的。


在我们使用函数式的流程控制之后只用了最后 4 行基础代码来实现这些功能,这也许使代码变得更可读了。或者也没有,毕竟这是一种主观判断。我仍然希望这篇文章能帮到一些人写出更好的代码。


P.S. 我把@decoratorignoreretry打包到了一个实际的项目中


P.P.S 其他控制流程抽象的例子有:underscore.js 中的函数操作,列表推导式和生成器表达式,模式匹配函数重载,装饰器缓存等等。

http://blog.psjay.com/posts/abstracting-control-flow/

sohu-dba

本文转载自:微信公众账号 - SOHU-DBA,版权归原作者所有!

声明:文章版权归原作者所有 部分文章转自互联网 如有侵权请联系 [邮箱地址] 删除

路过

雷人

握手

鲜花

鸡蛋

相关分类

返回顶部