前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >有趣的Code Poster

有趣的Code Poster

作者头像
张逸
发布2018-03-07 17:03:56
1K0
发布2018-03-07 17:03:56
举报
文章被收录于专栏:斑斓斑斓

Pete Corey的博客《Build your own code poster with Elixir》展示了如何通过Elixir实现一个类似Commits.io一样的功能,即可以将自己的代码融合到一张LOGO图片中,例如这样的效果:

左侧是原图,右侧则将代码重叠到图中有颜色的部分,形成一种颇有极客范儿的图片。这很有趣。

实现代码并不复杂,不到200行代码。虽然简单,代码却很好地体现了Elixir编程的风格。这种风格提倡运用|>管道符以流的形式传递数据,体现函数组合子的强大威力。针对问题域,我们的解决方案是思考数据流动的方向,以及处理数据的各个阶段,并将其组合成一个顺序的执行流程。

以Code Poster为例,其执行流程如下所示:

  • 读取代码文件
  • 读取图片文件
  • 生成Text元素集(这个过程会完成代码字符与颜色的融合)
  • 生成SVG
  • 保存SVG文件

Elixir的管道运算符,会自然地让我们想起Unix中实现的Pipe and Filter模式,每个Filter均被定义为一个统一的接口:输入为stdin,输出为stdout或者stderr。

在Unix中,这种统一接口可以被隐喻为文件(file),上图中的stdin、stdout与stderr都属于file descriptor,可以像操作文件那样读或写字节流(stream of bytes)。这里所谓的文件是一个宽泛的概念,可以是一个文件系统,也可以通过管道(pipe)将字节流传递到另一个进程,可以是Unix Socket,设备驱动,内核API以及TCP连接。

那么,在Code Poster场景下,这个统一接口是什么呢?或者说我们需要抽象的数据究竟是什么呢?分析前面的执行流程,读取一个code文件与读取一个image文件,返回的结果其实完全不同。那么,如这般不同的数据结构如何才能够像Stream一样通过管道连接起来呢?

在Elixir中,我们通常通过定义一个struct来完成对数据的抽象。整个管道处理中需要的数据会作为一个“并集”被定义到struct的属性中。例如,load code file操作需要一个code path,返回文件中的代码code;load image file操作则需要一个image path,返回一个image结构体。image结构体包含了图片的width和所有像素(pixels)。这些数据都是construct text element步骤需要的,在对这些数据进行处理后,该步骤又会返回生成的text elements集合;然后再交给construct svg,最后生成一个svg对象并保存。这个过程如下图所示:

整个过程传递的数据为PosterData,它好像是一个数据容器一般,各个执行步骤都会对该数据容器传来的相关数据进行处理,然后再将获得的结果塞到该容器中,再接力传递给下一个。PosterData的定义如下:

代码语言:javascript
复制
  defstruct code_path: nil,
            image_path: nil,
            ratio: nil,
            final_width: nil,
            final_height: nil,
            out_path: nil,
            code: nil,
            image: nil,
            pixels: nil,
            text_elements: nil,
            svg: nil

Elixir使用管道操作符|>非常好地改善了代码的阅读体验。阅读如下代码,与阅读自然语言没有太大区别:

代码语言:javascript
复制
  def go(ratio, final_width, final_height, code_path, image_path, out_path) do
    %PosterData{
      ratio: ratio,
      final_width: final_width,
      final_height: final_height,
      code_path: code_path,
      image_path: image_path,
      out_path: out_path
    }
    |> load_code
    |> load_image
    |> construct_text_elements
    |> construct_svg
    |> save_svg
  end

在编写这样的方法时,我们一定要注意方法的抽象层次,即遵循所谓的单一抽象层次原则。该原则要求一个函数中的所有操作都处于相同的抽象层。只有如此,才不会让函数表达的意思失衡,有的隐藏了细节,有的又暴露了不必要的内容。

我们可以通过对需求的任务逐层拆分来保证这一点。前面提到的流程是最高抽象层的任务分解,而针对load code file,又可以拆分为如下的子任务:

  • 读取代码文件
  • 去除文件内容中的空格、tab、换行符(表现的业务含义就是拼接这些代码字符)
  • 转换为字符集合

load_code函数体现了这样的流程:

代码语言:javascript
复制
  def load_code(data = %PosterData{code_path: code_path}) do
    Logger.debug("Loading code from '#{code_path}'...")
    code = code_path
    |> File.read!
    |> join_code
    |> String.codepoints
    %{data | code: code}
  end

其中join_code接收的是File.read!函数读取的文件内容,它又可以继续拆分子任务:

代码语言:javascript
复制
  def join_code(code) do
    Logger.debug("Joining code...")
    code
    |> String.trim
    |> String.replace(~r/\s*\n+\s*/, " ")
    |> String.replace(~r/\s/," ")
  end

多么美妙的代码表现力!又是多么清晰的任务分解层次!管道操作符将整个业务盘活了,就好像赋予了代码灵魂一般。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-03-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 逸言 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档