子清行
2014-01-12T14:20:28+08:00
http://zzp.me/
张泽鹏
redraiment@gmail.com
http://zzp.me/2013-12-02/jekyll-quick-start/
安装Jekyll
2013-12-02T14:45:41Z
2013-12-02T14:45:41Z
张泽鹏
http://zzp.me/
<h1>Mac OS X 用户</h1>
<p>如果你使用的是苹果电脑,那么恭喜你,Ruby等一切已经准备就绪,你所要做的就是在终端执行以下命令:</p>
<div class="highlight"><pre><code class="bash language-bash" data-lang="bash">gem install jekyll
</code></pre></div>
<h1>Linux 用户</h1>
<p>如果你用的是Linux,或许Ruby不是默认安装的,但安装起来也很方便。以Debian系统为例,安装Jekyll需要执行以下命令:</p>
<div class="highlight"><pre><code class="bash language-bash" data-lang="bash">apt-get install ruby ruby-dev
gem install jekyll
</code></pre></div>
<h1>Windows 用户</h1>
<p>如果你是Windows用户,这篇文章就是为你准备的!</p>
<h2>1. 下载Cygwin</h2>
<p>到<a href="http://www.cygwin.com">www.cygwin.com</a>下载最新的安装包:<a href="http://cygwin.com/setup-x86.exe">32位</a>、<a href="http://cygwin.com/setup-x86_64.exe">64位</a>。</p>
<h2>2. 安装Ruby和GCC</h2>
<p>启动第一步下载的安装包,在选择软件列表中选中Ruby,如下图所示:</p>
<p><img src="/resource/2013-12-02/jekyll-quick-start/1.png" /></p>
<p>使用相同的方法,安装gcc、make以及libcrypt-devel。</p>
<h2>3. 安装Jekyll</h2>
<p>经过以上的准备步骤,可以开始安装Jekyll了:</p>
<div class="highlight"><pre><code class="bash language-bash" data-lang="bash">gem install posix-spawn
gem install jekyll
</code></pre></div>
<h2>4. 降级pygments.rb</h2>
<p>安装完Jekyll后,默认会安装以来的pygments.rb库,这个库用于高亮代码。但最新版的pygments.rb无法在cygwin中正常使用,需要降级成0.5.0版本。</p>
<div class="highlight"><pre><code class="bash language-bash" data-lang="bash">gem uninstall pygments.rb --version <span class="s1">'>0.5.0'</span>
gem install pygments.rb --version 0.5.0
</code></pre></div>
http://zzp.me/2013-11-29/clearcase-workflows-cn/
ClearCase流程
2013-11-29T14:02:39Z
2013-11-29T14:02:39Z
张泽鹏
http://zzp.me/
<h1>过程模型</h1>
<p>ClearCase和ClearQuest实现了两种过程模型:Base和UCM(Unified Change Management,即统一变更管理),其中UCM是被推荐的方式。</p>
<p>Base模型基于配置管理解决方案的第二代理论,只能处理文件和目录。它使用分支(Branch)和视图(View)来支持并行开发,使用标签(Label)来标记每一次发布。</p>
<p>UCM基于第三代理论,引入了一些新的概念:活动(activity)、变更集(change set)、基线(baseline)、流(stream)等。它管理变更集和活动,而不是针对文件。</p>
<p>下表示两个模型的概念对比:</p>
<table><thead>
<tr>
<th>Base</th>
<th>UCM</th>
</tr>
</thead><tbody>
<tr>
<td>Base View</td>
<td>UCM View</td>
</tr>
<tr>
<td>/</td>
<td>Component</td>
</tr>
<tr>
<td>Branch</td>
<td>Stream</td>
</tr>
<tr>
<td>/</td>
<td>Activity</td>
</tr>
<tr>
<td>Label</td>
<td>Baseline</td>
</tr>
<tr>
<td>Element</td>
<td>Element</td>
</tr>
<tr>
<td>/</td>
<td>Change Set</td>
</tr>
</tbody></table>
<p>UCM概念简介:</p>
<ol>
<li>VOB (Version Object Base):是一个仓库,容纳文件、目录以及任何与产品或开发相关的资源。
<ul>
<li>Project VOB:保存VOB的元数据(Metadata)。例如项目、流、基线、活动、变更集等。</li>
<li>Components VOB:保存与开发、集成、发布等真正工作相关的文件、目录等信息。</li>
</ul></li>
<li>Stream:“流”为开发者提供独立的工作区。它包含一组基线,用于替代Base中分支的概念。开发人员可基于某一条基线创建一条开发流(Development Stream),而分支却只能创建单个文件的分支。</li>
<li>Baseline:“基线”可理解成快照,指明了某时刻组建的版本。它包含一个活动集,用于取代Base中的标签概念。</li>
<li>Activity:“活动”用于记录一项单独的任务,例如修复一个缺陷、添加一项新功能等。</li>
<li>Change Set:“变更集”与“活动”相关连,记录开发者为了完成活动中描述的任务而创建或修改的一系列文件。</li>
<li>Element:元素是版本库中的单个文件或目录,一个元素包含一到多个版本。</li>
</ol>
<h1>UCM工作流</h1>
<p>一个UCM典型的处理流程如下:</p>
<ol>
<li><p>ClearCase管理员创建VOBs</p>
<p>ClearCase管理员创建一个用于容纳所有开发相关资源的仓库。</p></li>
<li><p>项目主管创建UCM项目</p>
<p>项目主管在仓库中创建一个自己的项目,包含相关的组件,此时ClearCase自动自动生成项目的集成流(Integration Stream)。</p></li>
<li><p>开发人员加入(join)UCM项目</p>
<p>开发人员参与到项目中,并创建私人的工作区(开发流)。</p></li>
<li><p>执行开发任务</p>
<p>开发人员创建或委任一个活动,一个活动应该只用于一个开发任务!</p></li>
<li><p>开发人员交付(deliver)自己的成果到共享区</p>
<p>当开发人员完成了自己的开发任务,并完成相应的测试,就可以把自己的工作成果交付到集成流上。</p></li>
<li><p>发行工程师(Release engineering)构建项目</p>
<p>项目中专门负责构建的工程师会周期性地到集成流上构建最新的可执行文件,使其能包含开发人员交付的代码。</p></li>
<li><p>项目主管创建一条新的基线</p>
<p>如果项目构建成功,项目主管会打一条新的基线,然后由质量保证组的工程师们基于新的基线做集成测试。</p></li>
<li><p>项目主管把基线提升为里程碑</p>
<p>经过反复地修复、构建、测试、发布等过程,基线会稳定下来,此时项目主管可以去推荐(recommended)这条基线为项目里程碑。</p></li>
<li><p>开发人员把私人的工作区更新到最新状态</p>
<p>开发人员把私人的工作区更新到推荐的基线上,然后重复上述步骤继续新的开发任务。</p></li>
</ol>
<p>用于产品紧急修复(hot fix)的流程如下:</p>
<ol>
<li><p>开发人员创建一条用于紧急修复的开发流</p>
<p>开发人员基于发布的基线(不是最新的基线)创建一条用于紧急修复的开发流。</p></li>
<li><p>执行开发任务</p>
<p>把补丁打到新的开发流中。</p></li>
<li><p>项目主管在该开发上打基线</p>
<p>在开发人员完成测试后,项目主管在该开发流上创建一条新的基线,它仅包含已发布的代码和补丁代码,不会包含那些仍然处于开发过程中的未发布代码。因此把这条基线提升为里程碑作为紧急发布版本。</p></li>
<li><p>开发人员把补丁交付到集成流中。</p>
<p>根据上述流程,整合补丁代码到当前开发中。</p></li>
</ol>
http://zzp.me/2013-11-29/clearcase-workflows/
ClearCase Workflows
2013-11-29T13:03:19Z
2013-11-29T13:03:19Z
张泽鹏
http://zzp.me/
<h1>Process Model</h1>
<p>There are two process models that have been implemented on top of ClearCase and ClearQuest: Base and UCM (Unified Change Management), and UCM is recommended in State Street Corporation.</p>
<p>Base ClearCase, as implement of the second generation theory of configuration management solution, can only deal with Files and Directories. It uses branch and view to support parallel development, and tag a label for each release.</p>
<p>UCM ClearCase, as implement of the third generation theory of SCM solution, bring in some new concepts, activity, change set, baseline, stream etc. It manages change set and activity rather than files.</p>
<table><thead>
<tr>
<th>Base</th>
<th>UCM</th>
</tr>
</thead><tbody>
<tr>
<td>Base View</td>
<td>UCM View</td>
</tr>
<tr>
<td>/</td>
<td>Component</td>
</tr>
<tr>
<td>Branch</td>
<td>Stream</td>
</tr>
<tr>
<td>/</td>
<td>Activity</td>
</tr>
<tr>
<td>Label</td>
<td>Baseline</td>
</tr>
<tr>
<td>Element</td>
<td>Element</td>
</tr>
<tr>
<td>/</td>
<td>Change Set</td>
</tr>
</tbody></table>
<p>Brief concept for UCM:</p>
<ol>
<li>VOB (Version Object Base): is a data repository that holds the files, directories, and any objects that collectively represent a product or a development effort.
<ul>
<li>Project VOB: keeps the metadata of VOB. Like projects, stream, baseline, activity, change set etc.</li>
<li>Components VOB: is a group of related directory and file elements, which are developed, integrated, and released together. </li>
</ul></li>
<li>Stream: is private work area of developers. It contains a set of baselines, uses to instead branch. Developers can create a development stream base on a baseline for whole project, while branch can only create a branch for single file.</li>
<li>Baseline: represents a version of one or more components. It contains a set of activities. To instead label in base.</li>
<li>Activity: records a set of files that a developer creates or modifies to complete a development task, such as fixing a bug.</li>
<li>Change Set: is a set of files associated with an activity.</li>
<li>Element: is a file or directory. An element contains one or more versions.</li>
</ol>
<h1>UCM Workflows</h1>
<p>In a UCM project, work typically progresses as follows:</p>
<ol>
<li><p>ClearCase administrators create the VOBs. </p>
<p>ClearCase Administrators create the VOBs that will contain all the files and directories that represent the development effort, such as a new product release.</p></li>
<li><p>Project managers create the UCM project. </p>
<p>Project manager create a UCM project and identify an initial set of components as a starting point (the baseline based on).</p></li>
<li><p>Developers join the UCM project.</p>
<p>Developers join the project by creating their private work areas and populating them with the contents of the project's baselines.</p></li>
<li><p>Development is ongoing. </p>
<p>Developers create, or are assigned, activities, and work on one activity at a time.</p></li>
<li><p>Developers deliver work to the shared work area. </p>
<p>When developers complete activities, they build and test their work in their private work areas (Development Stream). When developers create a successful software build in their private work area, they share their work with the project team by performing deliver operations. A deliver operation merges the work from the developer's private work area to the project's shared work area (Integration Stream).</p></li>
<li><p>Release engineering builds the product.</p>
<p>Periodically, the delivered work from developers is integrated by building the project's executable files in the shared work area. This work is typically done by an individual in the development group who is assigned to build the product.</p></li>
<li><p>Project managers create new baselines. </p>
<p>If the project builds successfully, project manager create a new set of baselines. In a separate work area, a team of Quality Engineers performs more extensive testing of the new baselines.</p></li>
<li><p>Project managers promote specific baselines that reflect a particular project milestone.</p>
<p>Periodically, as the quality and stability of baselines improve, project manager adjust the promotion level attribute of the baselines to reflect appropriate milestones, such as Built, Tested, or Released. When the new baselines pass a sufficient level of testing, project manager designate them as the recommended set of baselines</p></li>
<li><p>Developers adjust their private work areas to the latest available baselines.</p>
<p>Developers perform rebase operations to update their private work areas to include the set of versions represented by the new recommended baselines.</p>
<p>Developers continue the cycle of working on activities, delivering completed activities, and updating their private work areas with new baselines.</p></li>
</ol>
<p>For production hot fix process:</p>
<ol>
<li><p>Developers create a hot fix development stream</p>
<p>Developers create a new development stream base on the production baseline</p></li>
<li><p>Development is ongoing</p>
<p>Developer applies hot fix changes on new stream.</p></li>
<li><p>Project manager create a new baseline on HOT FIX stream</p>
<p>When developers complete changes, they build and test their work in their private work areas. Then, project manager create a baseline on HOT FIX stream. The new baseline contains production release and hot fix changes only.</p></li>
<li><p>Developers deliver hot fix changes to current development stream</p>
<p>To merge the hot fix to current develop task.</p></li>
</ol>
http://zzp.me/2013-11-28/iknowledge-v2-wiki/
开始使用iKnowledge
2013-11-28T21:48:44Z
2013-11-28T21:48:44Z
张泽鹏
http://zzp.me/
<h1>Q: 如何修改网站信息?</h1>
<p>开始使用iKnowledge之前先要配置一些基本信息,例如博客的名称、域名地址等。</p>
<p>所有博客相关的信息都在文件<code>_data/blog.yml</code>中:</p>
<div class="highlight"><pre><code class="yaml language-yaml" data-lang="yaml"><span class="l-Scalar-Plain">name</span><span class="p-Indicator">:</span> <span class="l-Scalar-Plain">博客名</span>
<span class="l-Scalar-Plain">motto</span><span class="p-Indicator">:</span> <span class="l-Scalar-Plain">座右铭</span>
<span class="l-Scalar-Plain">url</span><span class="p-Indicator">:</span> <span class="l-Scalar-Plain">个人域名</span>
<span class="l-Scalar-Plain">author</span><span class="p-Indicator">:</span>
<span class="l-Scalar-Plain">id</span><span class="p-Indicator">:</span> <span class="l-Scalar-Plain">网名</span>
<span class="l-Scalar-Plain">name</span><span class="p-Indicator">:</span> <span class="l-Scalar-Plain">真实姓名</span>
<span class="l-Scalar-Plain">email</span><span class="p-Indicator">:</span> <span class="l-Scalar-Plain">电子邮件</span>
</code></pre></div>
<p>把<code>博客名</code>等内容修改成你自己的个人信息,它们会出现在页面的相应位置。</p>
<p><em>注意</em>:如果你没有自己的独立域名,请删除<code>url</code>这一行;如果设置了自己的域名,iKnowledge会在<code>_site</code>目录下额外生成一个名为<code>CNAME</code>的文件,用于Github Pages等。</p>
<h1>Q: 如何自定义层级分类列表?</h1>
<p>层级分类列表的信息保存在文件<code>_data/categories.yml</code>中,与<code>_data/blog.yml</code>一样,这也是一个<a href="http://zh.wikipedia.org/zh-cn/YAML">YAML</a>文档:</p>
<div class="highlight"><pre><code class="yaml language-yaml" data-lang="yaml"><span class="p-Indicator">-</span> <span class="l-Scalar-Plain">id</span><span class="p-Indicator">:</span> <span class="l-Scalar-Plain">iKnowledge</span>
<span class="l-Scalar-Plain">excerpt</span><span class="p-Indicator">:</span> <span class="l-Scalar-Plain">iKnowledge使用手册</span>
<span class="l-Scalar-Plain">children</span><span class="p-Indicator">:</span>
<span class="p-Indicator">-</span> <span class="l-Scalar-Plain">id</span><span class="p-Indicator">:</span> <span class="l-Scalar-Plain">v1</span>
<span class="l-Scalar-Plain">name</span><span class="p-Indicator">:</span> <span class="l-Scalar-Plain">第一版</span>
<span class="p-Indicator">-</span> <span class="l-Scalar-Plain">id</span><span class="p-Indicator">:</span> <span class="l-Scalar-Plain">v2</span>
<span class="l-Scalar-Plain">name</span><span class="p-Indicator">:</span> <span class="l-Scalar-Plain">第二版</span>
<span class="p-Indicator">-</span> <span class="l-Scalar-Plain">id</span><span class="p-Indicator">:</span> <span class="l-Scalar-Plain">友情链接</span>
</code></pre></div>
<p>每个字段的意义如下:</p>
<ul>
<li>id:必须的字段。必须全局唯一,用于每篇文章的分类(category)。</li>
<li>name:用于在页面上展示的名字,如果该字段未指定,系统使用<code>id</code>字段代替。该字段并不要唯一性。</li>
<li>excerpt:简短的描述,介绍该分类的内容。该描述会现在是分类页面上。</li>
<li>children: 子类别(如果有的话)。</li>
</ul>
<h1>Q: 如何写文章?</h1>
<p>新的文章必须放在_posts目录下,并且文件名格式为:</p>
<div class="highlight"><pre><code class="text language-text" data-lang="text">YEAR-MONTH-DAY-title.md
</code></pre></div>
<p>其中YEAR是四位数,MONTH和DAY是两位数,title是文章标题。例如以下文件名都是合法的:</p>
<div class="highlight"><pre><code class="text language-text" data-lang="text">2013-03-13-iknowledge-v1-release.md
2013-11-16-iknowledge-v2-release.md
2013-11-27-iknowledge-users.md
2013-11-28-iknowledge-v2-getting-started.md
2013-11-28-iknowledge-v2-wiki.md
</code></pre></div>
<p><code>.md</code>扩展名表明文章采用Markdown格式,如果你还不熟悉Markdown语法,请参考<a href="http://wowubuntu.com/markdown/">《Mark语法说明(简体中文版)》</a>。</p>
<h1>Q: 如何插入图片?</h1>
<p>按照习惯,图片放在资源目录(resource)下,你可以使用Markdown的语法在文章中插入图片:</p>
<div class="highlight"><pre><code class="text language-text" data-lang="text">![Avatar](/resource/image/avatar.gif)
</code></pre></div>
<p>效果如下:</p>
<p><img src="resource/image/avatar.gif" alt="Avatar"></p>
<p>iKnowledge还提供了一种额外的方式插入图片。如果图片放置的路径为:</p>
<div class="highlight"><pre><code class="text language-text" data-lang="text">resource/YEAR-MONTH-DAY/title/
</code></pre></div>
<p>其中<code>YEAR</code>、<code>MONTH</code>、<code>DAY</code>以及<code>title</code>的值均与当前文章的文件名一致,就能使用<code>img</code>标签来插入图片。例如本文的文件名是<code>2013-11-28-iknowledge-v2-wiki.md</code>,另外有一张图片<code>1.gif</code>放置在<code>resource/2013-11-28/iknowledge-v2-wiki/</code>目录下,在文章中使用:</p>
<pre>{% img 1.gif %}</pre>
<p>即可插入图片。</p>
<h1>Q: 如何使用修改历史插件?</h1>
<p><code>_plugins/GitLog.rb</code>依赖grit库,iKnowledge通过它来获取文章的历史信息。因此需要先安装grit,执行下列命令:</p>
<div class="highlight"><pre><code class="bash language-bash" data-lang="bash"><span class="nv">$ </span>gem install grit
</code></pre></div>
<p>grit是一个用于操作git的ruby库,即使没有安装grit,iKnowledge依旧能正常使用,只是文章末尾不会生成修改历史。</p>
<h1>Q: 下一步?</h1>
<p>因为iKnowledge就是Jekyll,所有Jekyll的功能都照常能用。想了解iKnowledge更多的功能,请参考<a href="http://jekyllrb.com/docs/home/">Jekyll在线文档</a>。</p>
http://zzp.me/2013-11-28/iknowledge-v2-getting-started/
一分钟安装iKnowledge
2013-11-28T21:18:23Z
2013-11-28T21:18:23Z
张泽鹏
http://zzp.me/
<h1>1. 安装Jekyll</h1>
<div class="highlight"><pre><code class="bash language-bash" data-lang="bash"><span class="nv">$ </span>gem install jekyll
</code></pre></div>
<p>因为iKnowledge基于<a href="http://jekyllrb.com/">Jekyll</a>,因此这一步是必需完成的。如果上述命令在你的环境中不能成功执行,请参考<a href="/2013-12-02/jekyll-quick-start/">jekyll安装指南</a>。</p>
<h1>2. 下载iKnowledge</h1>
<div class="highlight"><pre><code class="bash language-bash" data-lang="bash"><span class="nv">$ </span>wget https://github.com/redraiment/iKnowledge/archive/master.zip
<span class="nv">$ </span>ls
master.zip
</code></pre></div>
<h1>3. 安装iKnowledge</h1>
<div class="highlight"><pre><code class="bash language-bash" data-lang="bash"><span class="nv">$ </span>unzip master.zip
<span class="nv">$ </span>ls
iKnowledge-master master.zip
</code></pre></div>
<p>iKnowledge的安装非常简单,只需解压刚刚下载的压缩包即可!</p>
<h1>4. 运行iKnowledge</h1>
<p>和其他jekyll网站一样,你可以通过jekyll serve来运行:</p>
<div class="highlight"><pre><code class="bash language-bash" data-lang="bash"><span class="nv">$ </span><span class="nb">cd </span>iKnowledge-master
<span class="nv">$ </span>jekyll serve
Configuration file: /home/redraiment/iKnowledge-master/_config.yml
Source: /home/redraiment/iKnowledge-master
Destination: /home/redraiment/iKnowledge-master/_site
Generating... <span class="k">done</span>.
Server address: http://0.0.0.0:4000
Server running... press ctrl-c to stop.
</code></pre></div>
<p>此时,打开网页浏览器,访问<code>http://localhost:4000</code>,如果能正常看到首页,恭喜你已经成功安装并运行iKnowledge!</p>
<h1>5. 下一步</h1>
<p><a href="/2013-11-28/iknowledge-v2-wiki/">开始使用iKnowledge</a>!</p>
http://zzp.me/2013-11-28/db2-neighbour-rows/
获取相邻行的内容
2013-11-28T16:55:26Z
2013-11-28T16:55:26Z
张泽鹏
http://zzp.me/
<p>DB2提供了两个函数,允许在查询时获取上一行/下一行的数据</p>
<h1>往上找</h1>
<p><code>LAG(表达式或字段, 偏移量, 默认值, IGNORE NULLS或RESPECT NULLS)</code></p>
<h1>往下找</h1>
<p><code>LEAD(表达式或字段, 偏移量, 默认值, IGNORE NULLS或RESPECT NULLS)</code></p>
http://zzp.me/2013-11-28/db2-meta-data/
查看元数据
2013-11-28T16:15:33Z
2013-11-28T16:15:33Z
张泽鹏
http://zzp.me/
<h1>查看版本信息</h1>
<div class="highlight"><pre><code class="sql language-sql" data-lang="sql"><span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="n">sysibmadm</span><span class="p">.</span><span class="n">env_inst_info</span>
</code></pre></div>
<h1>查看所有表</h1>
<div class="highlight"><pre><code class="sql language-sql" data-lang="sql"><span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="n">syscat</span><span class="p">.</span><span class="n">tables</span>
</code></pre></div>
<h1>查看所有列</h1>
<div class="highlight"><pre><code class="sql language-sql" data-lang="sql"><span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="n">syscat</span><span class="p">.</span><span class="n">columns</span>
</code></pre></div>
<h1>查看所有函数</h1>
<div class="highlight"><pre><code class="sql language-sql" data-lang="sql"><span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="n">syscat</span><span class="p">.</span><span class="n">functions</span>
</code></pre></div>
<h1>查看所有存储过程</h1>
<div class="highlight"><pre><code class="sql language-sql" data-lang="sql"><span class="k">select</span>
<span class="n">RoutineName</span><span class="p">,</span>
<span class="nb">text</span>
<span class="k">from</span>
<span class="n">syscat</span><span class="p">.</span><span class="n">routines</span>
<span class="k">where</span>
<span class="n">RoutineName</span> <span class="o">=</span> <span class="s1">'XXX'</span>
</code></pre></div>
http://zzp.me/2013-11-28/db2-command-line-tools/
db2命令
2013-11-28T15:56:20Z
2013-11-28T15:56:20Z
张泽鹏
http://zzp.me/
<div class="highlight"><pre><code class="bash language-bash" data-lang="bash"><span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="nv">$PATH</span>:<span class="nv">$DB2_HOME</span>/bin
</code></pre></div>
<p>其中DB2_HOME是DB2 Client所在的目录</p>
<h1>连接数据库</h1>
<div class="highlight"><pre><code class="bash language-bash" data-lang="bash"><span class="c"># 查看可用DB</span>
db2 list db directory
<span class="c"># 连接DB,其中$DB_DIR由上条命令获得</span>
db2 connect to <span class="nv">$DB_DIR</span> user <span class="nv">$USER</span> using <span class="nv">$UNIX_PASSWORD</span>
</code></pre></div>
<h1>导出查询结果为csv文件</h1>
<div class="highlight"><pre><code class="bash language-bash" data-lang="bash">db2 <span class="nb">export </span>to data.csv of DEL <span class="k">select</span> <span class="se">\*</span> from table
</code></pre></div>
<h1>从csv文件中导入数据到数据库</h1>
<div class="highlight"><pre><code class="bash language-bash" data-lang="bash">db2 import from data.csv of DEL insert into table
</code></pre></div>
<h1>执行脚本</h1>
<p>即批量执行代码,通常用于创建/更新SP、table、function、view等。</p>
<div class="highlight"><pre><code class="bash language-bash" data-lang="bash">db2 -td^ -svf script.sql
</code></pre></div>
<p>其中 -td^ 指定分隔符用“^”而不是用默认的“;”,这在创建SP时使用。</p>
<h1>断开连接</h1>
<div class="highlight"><pre><code class="bash language-bash" data-lang="bash">db2 quit
</code></pre></div>
http://zzp.me/2013-11-28/db2-table-autoincrement/
Reset table autoincrement column
2013-11-28T15:48:58Z
2013-11-28T15:48:58Z
张泽鹏
http://zzp.me/
<h1>创建自动增长列</h1>
<div class="highlight"><pre><code class="sql language-sql" data-lang="sql"><span class="k">create</span> <span class="k">table</span> <span class="k">table_name</span> <span class="p">(</span>
<span class="n">field_name</span> <span class="nb">int</span> <span class="k">not</span> <span class="k">null</span> <span class="k">generated</span> <span class="k">by</span> <span class="k">default</span> <span class="k">as</span> <span class="k">identity</span>
<span class="p">)</span>
</code></pre></div>
<h1>查看自动增长列当前值</h1>
<div class="highlight"><pre><code class="sql language-sql" data-lang="sql"><span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="n">sysibm</span><span class="p">.</span><span class="n">syssequences</span> <span class="k">where</span> <span class="n">seqid</span> <span class="o">=</span> <span class="p">(</span>
<span class="k">select</span> <span class="n">seqid</span> <span class="k">from</span> <span class="n">syscat</span><span class="p">.</span><span class="n">colidentattributes</span> <span class="k">where</span> <span class="n">tabname</span> <span class="o">=</span> <span class="s1">'XXX'</span>
<span class="p">)</span>
</code></pre></div>
<h1>重置自动增长列开始值</h1>
<div class="highlight"><pre><code class="sql language-sql" data-lang="sql"><span class="k">alter</span> <span class="k">table</span> <span class="n">XXX</span> <span class="k">alter</span> <span class="n">XXX_id</span> <span class="k">restart</span> <span class="k">with</span> <span class="mi">123</span><span class="p">;</span>
</code></pre></div>
<h1>创建自动增长数列</h1>
<p>有时候需要给查询的语句标记上递增的序号。例如第一行标记为1、第二行标记为2……</p>
<p>该方法性能略微优于子查询,但其实一样的差,即不建议使用!</p>
<div class="highlight"><pre><code class="sql language-sql" data-lang="sql"><span class="k">create</span> <span class="n">sequence</span> <span class="n">order_seq</span> <span class="k">start</span> <span class="k">with</span> <span class="mi">100</span> <span class="k">increment</span> <span class="k">by</span> <span class="mi">1</span> <span class="n">nomaxvalue</span> <span class="n">nocycle</span> <span class="k">cache</span> <span class="mi">24</span><span class="p">;</span>
<span class="k">insert</span> <span class="k">into</span> <span class="n">joe_test</span> <span class="p">(</span>
<span class="n">id</span><span class="p">,</span>
<span class="n">name</span>
<span class="p">)</span>
<span class="k">select</span>
<span class="n">nextval</span> <span class="k">for</span> <span class="n">order_seq</span> <span class="k">as</span> <span class="n">id</span><span class="p">,</span>
<span class="n">name</span>
<span class="k">from</span>
<span class="n">name_temp</span><span class="p">;</span>
<span class="k">drop</span> <span class="n">sequence</span> <span class="n">order_seq</span><span class="p">;</span>
</code></pre></div>
http://zzp.me/2013-11-28/db2-sp-schema/
APS SP设置schema
2013-11-28T15:43:44Z
2013-11-28T15:43:44Z
张泽鹏
http://zzp.me/
<p>DB2需要同时设置schema和current path,否则访问不到某些函数或SP。</p>
<div class="highlight"><pre><code class="sql language-sql" data-lang="sql"><span class="k">set</span> <span class="k">schema</span> <span class="err">$</span><span class="k">SCHEMA_NAME</span><span class="p">;</span>
<span class="k">set</span> <span class="k">current</span> <span class="n">path</span> <span class="n">PATH_NAME1</span><span class="p">,</span><span class="n">PATH_NAME2</span><span class="p">;</span>
</code></pre></div>
http://zzp.me/2013-11-28/db2-sp-grant/
给SP赋权限
2013-11-28T15:23:00+08:00
2013-11-28T15:23:00+08:00
张泽鹏
http://zzp.me/
<h1>查看权限</h1>
<h2>查看表权限</h2>
<div class="highlight"><pre><code class="sql language-sql" data-lang="sql"><span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="n">syscat</span><span class="p">.</span><span class="n">TabAuth</span> <span class="k">where</span> <span class="n">TabName</span> <span class="o">=</span> <span class="s1">'XXX'</span>
</code></pre></div>
<h2>查看SP权限</h2>
<div class="highlight"><pre><code class="sql language-sql" data-lang="sql"><span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="n">syscat</span><span class="p">.</span><span class="n">RoutineAuth</span> <span class="k">where</span> <span class="n">SpecificName</span> <span class="o">=</span> <span class="p">(</span>
<span class="k">select</span> <span class="n">SpecificName</span> <span class="k">from</span> <span class="n">syscat</span><span class="p">.</span><span class="n">Routines</span> <span class="k">where</span> <span class="n">RoutineName</span> <span class="o">=</span> <span class="s1">'XXX'</span>
<span class="p">)</span>
</code></pre></div>
<h1>修改权限</h1>
<div class="highlight"><pre><code class="sql language-sql" data-lang="sql"><span class="k">grant</span> <span class="k">execute</span> <span class="k">on</span> <span class="k">procedure</span> <span class="p">[</span><span class="n">SP</span> <span class="n">NAME</span><span class="p">]</span> <span class="k">to</span> <span class="k">group</span> <span class="err">$</span><span class="n">GROUP_ID</span><span class="p">;</span>
<span class="k">grant</span> <span class="k">execute</span> <span class="k">on</span> <span class="k">procedure</span> <span class="p">[</span><span class="n">SP</span> <span class="n">NAME</span><span class="p">]</span> <span class="k">to</span> <span class="k">user</span> <span class="err">$</span><span class="n">USER_ID</span> <span class="k">with</span> <span class="k">grant</span> <span class="k">option</span><span class="p">;</span>
<span class="k">grant</span> <span class="k">execute</span> <span class="k">on</span> <span class="k">function</span> <span class="p">[</span><span class="n">FN</span> <span class="n">NAME</span><span class="p">]</span> <span class="k">to</span> <span class="k">group</span> <span class="err">$</span><span class="n">GROUP_ID</span><span class="p">;</span>
<span class="k">grant</span> <span class="k">execute</span> <span class="k">on</span> <span class="k">function</span> <span class="p">[</span><span class="n">FN</span> <span class="n">NAME</span><span class="p">]</span> <span class="k">to</span> <span class="k">user</span> <span class="err">$</span><span class="n">USER_ID</span> <span class="k">with</span> <span class="k">grant</span> <span class="k">option</span><span class="p">;</span>
</code></pre></div>
http://zzp.me/2013-11-16/iknowledge-v2-release/
iKnowledge 2.0 发布:把事情做对!
2013-11-16T00:21:00+08:00
2013-11-16T00:21:00+08:00
张泽鹏
http://zzp.me/
<p>年初我开发了<a href="/2013-03-13/iknowledge-v1-release/">iKnowledge</a>,当时为了快速地完成从无到有的突破,我选择基于ExtJS来实现(稍纵即逝的灵感很容易被惰性打败)。在这大半年里,我依旧不断地学习新技术,以前一直下不了决心学习Python、<a href="http://ruby-lang.org/">Ruby</a>等主流脚本语言,现在也开始使用并喜欢上它们。在仔细阅读了<a href="http://jekyllrb.com/docs/home/">Jekyll</a>的官方文档后,我觉得时机已经成熟,便开始动手开发新版的iKnowledge!</p>
<p>经过几夜奋斗,iKnowledge 2.0终于完成,实现了许多我梦寐以求的功能!新版本之于旧版本,犹如SVN之于CVS:iKnowledge 1.0是伟大的软件,它从无到有的创新(当然也许早就存在我不知道的同类软件),但同时它不是一款好软件,安装繁琐、使用麻烦、体态臃肿……;iKnowledge 2.0站在巨人的肩膀之上,从软件的安装到使用,许多方面都得到了改善,它努力成为一款好用的软件!</p>
<p>再次感谢<a href="http://jekyllrb.com/">Jekyll</a>,感谢<a href="http://ruby-lang.org/">Ruby</a>!如果没有这些优秀的开源软件,道路会曲折很多。</p>
<h1>iKnowledge 的改进</h1>
<h2>更简便的安装</h2>
<p>第一版可谓是东拼西凑的产物,很多功能都停留在“能工作”的状态。例如从零搭建一套iKnowledge v1.0,你需要在本地安装Java、Shell环境、下载Rhino……门槛相当高。</p>
<p>新版本努力在这方面做得更好,新版本不会图一时便利而引入额外的工具或库。你需要做的仅仅是执行</p>
<div class="highlight"><pre><code class="text language-text" data-lang="text">git clone --depth 1 https://github.com/redraiment/iKnowledge.git
</code></pre></div>
<p>然后开始写文章!</p>
<h2>超轻量级</h2>
<p>iKnowledge终于摆脱了“击败全球99%的龟速博客”称号。现在,包括<a href="http://ruby-lang.org/">Ruby</a>开发的<a href="http://jekyllrb.com/">Jekyll</a>插件在内,整套系统只有100KB!单张页面不到2KB,比起一些刻意为移动设备优化过的网页还要小!</p>
<p>同时,iKnowledge还从重度依赖JavaScript的极端走向无JavaScript的另一个极端!第一版只有一张页面,所有文章的内容都通过AJAX动态加载,对搜索引擎的爬虫不够友好,导致站内搜索功能形同虚设;新版则是真正生成了纯静态的页面,每篇文章都是独立页面。</p>
<h2>分类归档</h2>
<ol>
<li>分类依旧可嵌套:第一版本中已实现;</li>
<li>同一篇文章可属于多个分类:由于第一版的分层结构依赖文件目录结构,因此一篇文章只隶属于一种分类;</li>
<li>文章所属分类变化时不影响文章的URL:第一版分类结构变化时URL也会随之变化,理由同上;</li>
<li>文章名不再出现在分类列表中:第一版分类与文章都放在同一颗树中,新版分类列表中不再包含文章链接,取而代之的是点击分类链接时显示该分类下的文章列表。</li>
</ol>
<h2>Markdown</h2>
<p>自2010年参加工作以来,我的CSDN博客几乎停止更新,偶尔发点小打小闹的东西。因为我转战线下:改用Emacs自带的org-mode来写文章,这更能让我专注于文章的内容。这半年里除了学习新的编程技术,我还掌握了Markdown格式,我发现它比org-mode更适合写文章,而且<a href="http://jekyllrb.com/">Jekyll</a>默认就能处理!</p>
<p>第一版中写文章需要手工书写HTML,遇到需要贴代码的情况还要手工转移特殊字符,这些琐事很影响我写作的心情和欲望。现在换成Markdown之后,它简洁的语法让我更有写作的冲动!</p>
<h2>修改历史</h2>
<p>这是iKnowledge的特色功能之一!即自动在每篇文章的末尾追加修改历史。</p>
<p>早在2009年,我就向CSDN博客维护团队建议过添加这个功能,但我很能理解他们否决的理由,这个功能很多人都用不上,甚至有些人还排斥这个功能。当时我要求添加这个功能的初衷是有很多网友在看我写的文章时会发现其中有错别字或其他错误,我希望有一个地方能统一展示有哪些人为这篇文章做出贡献。</p>
<p>另一个原因和我写文章的方式有关,我写文章通常不是一气呵成,往往从灵感乍现,到下笔写作,最后成文发布要经历很长的一段时间。例如我开发“中文计算器”这个小工具不是某一天一拍脑袋就想出来的,而是早在一年前研究阿拉伯数字转中文大写的算法,之后又研究中文转阿拉伯数字,最有才想到做成一个中文的计算器。我希望能完整地记录这个过程。</p>
<h2>社交分享</h2>
<p>最后,感谢新浪微博提供的评论箱功能,让静态页面也拥有评论的功能!同时集成微博分享功能,因此新版本中微博分享的按钮已经取消了。</p>
http://zzp.me/2013-09-05/y-combinator/
10种编程语言实现Y组合子
2013-09-05T10:28:39Z
2013-09-05T10:28:39Z
张泽鹏
http://zzp.me/
<h1>Y-Combinator</h1>
<p>转眼又过了一年,好像每年这个时候我都要和匿名递归函数较劲。今年我掌握了更多函数式编程的理论知识,了解到“匿名递归函数”已经有相应的研究结果:Y组合子(Y-Combinator)。</p>
<p>Y组合子是Lambda演算的一部分,也是函数式编程的理论基础。它是一种方法/技巧,在没有赋值语句的前提下定义递归的匿名函数。即仅仅通过Lambda表达式这个最基本的“原子”实现循环/迭代,颇有道生一、一生二、二生三、三生万物的感觉。</p>
<h2>从递归的阶乘函数开始</h2>
<p>先不考虑效率等其他因素,写一个最简单的递归阶乘函数。此处采用Scheme,你可以选择自己熟悉的编程语言跟着我一步一步实现Y-Combinator版的阶乘函数。</p>
<div class="highlight"><pre><code class="scheme language-scheme" data-lang="scheme"><span class="p">(</span><span class="k">define </span><span class="p">(</span><span class="nf">fab</span> <span class="nv">n</span><span class="p">)</span>
<span class="p">(</span><span class="k">if </span><span class="p">(</span><span class="nb">zero? </span><span class="nv">n</span><span class="p">)</span>
<span class="mi">1</span>
<span class="p">(</span><span class="nb">* </span><span class="nv">n</span> <span class="p">(</span><span class="nf">fab</span> <span class="p">(</span><span class="nb">- </span><span class="nv">n</span> <span class="mi">1</span><span class="p">)))))</span>
</code></pre></div>
<p>Scheme中<code>(define (fn-name))</code>是<code>(define fn-name (lambda))</code>的简写,就像JS中,<code>function foo() {}</code>等价于<code>var foo = function() {}</code>。把上面的定义展开成Lambda的定义:</p>
<div class="highlight"><pre><code class="scheme language-scheme" data-lang="scheme"><span class="p">(</span><span class="k">define </span><span class="nv">fab</span>
<span class="p">(</span><span class="k">lambda </span><span class="p">(</span><span class="nf">n</span><span class="p">)</span>
<span class="p">(</span><span class="k">if </span><span class="p">(</span><span class="nb">zero? </span><span class="nv">n</span><span class="p">)</span>
<span class="mi">1</span>
<span class="p">(</span><span class="nb">* </span><span class="nv">n</span> <span class="p">(</span><span class="nf">fab</span> <span class="p">(</span><span class="nb">- </span><span class="nv">n</span> <span class="mi">1</span><span class="p">))))))</span>
</code></pre></div>
<h2>绑定函数名</h2>
<p>想要递归地调用一个函数,就必须给这个函数取一个名字。匿名函数想要实现递归,就得取一个临时的名字。所谓临时,指这个名字只在此函数体内有效,函数执行完成后,这个名字就伴随函数一起消失。为解决这个问题,<a href="/2011-08-05/recursive-lambda/">第一篇文章</a>中强制规定匿名函数有一个隐藏的名字this指向自己,这导致this这个变量名被强行占用,并不优雅,因此<a href="/2012-08-04/clojure-style-lambda-in-common-lisp/">第二篇文章</a>借鉴Clojure的方法,允许自定义一个名字。</p>
<p>但在lambda演算中,只有最普通的lambda,没有赋值语句,如何绑定一个名字呢?答案是使用lambda的参数列表!</p>
<div class="highlight"><pre><code class="scheme language-scheme" data-lang="scheme"><span class="p">(</span><span class="k">lambda </span><span class="p">(</span><span class="nf">fab</span><span class="p">)</span>
<span class="p">(</span><span class="k">lambda </span><span class="p">(</span><span class="nf">n</span><span class="p">)</span>
<span class="p">(</span><span class="k">if </span><span class="p">(</span><span class="nb">zero? </span><span class="nv">n</span><span class="p">)</span>
<span class="mi">1</span>
<span class="p">(</span><span class="nb">* </span><span class="nv">n</span> <span class="p">(</span><span class="nf">fab</span> <span class="p">(</span><span class="nb">- </span><span class="nv">n</span> <span class="mi">1</span><span class="p">))))))</span>
</code></pre></div>
<h2>生成阶乘函数的函数</h2>
<p>虽然通过参数列表,即使用闭包技术给匿名函数取了一个名字,但此函数并不是我们想要的阶乘函数,而是阶乘函数的元函数(meta-fab),即生成阶乘函数的函数。因此需要执行这个元函数,获得想要的阶乘函数:</p>
<div class="highlight"><pre><code class="scheme language-scheme" data-lang="scheme"><span class="p">((</span><span class="k">lambda </span><span class="p">(</span><span class="nf">fab</span><span class="p">)</span>
<span class="p">(</span><span class="k">lambda </span><span class="p">(</span><span class="nf">n</span><span class="p">)</span>
<span class="p">(</span><span class="k">if </span><span class="p">(</span><span class="nb">zero? </span><span class="nv">n</span><span class="p">)</span>
<span class="mi">1</span>
<span class="p">(</span><span class="nb">* </span><span class="nv">n</span> <span class="p">(</span><span class="nf">fab</span> <span class="p">(</span><span class="nb">- </span><span class="nv">n</span> <span class="mi">1</span><span class="p">))))))</span>
<span class="nv">xxx</span><span class="p">)</span>
</code></pre></div>
<p>此时又出现另一个问题:实参xxx,即形参fab该取什么值?从定义来看,fab就是函数自身,既然是“自身”,首先想到的就是复制一份一模一样的代码:</p>
<div class="highlight"><pre><code class="scheme language-scheme" data-lang="scheme"><span class="p">((</span><span class="k">lambda </span><span class="p">(</span><span class="nf">fab</span><span class="p">)</span>
<span class="p">(</span><span class="k">lambda </span><span class="p">(</span><span class="nf">n</span><span class="p">)</span>
<span class="p">(</span><span class="k">if </span><span class="p">(</span><span class="nb">zero? </span><span class="nv">n</span><span class="p">)</span>
<span class="mi">1</span>
<span class="p">(</span><span class="nb">* </span><span class="nv">n</span> <span class="p">(</span><span class="nf">fab</span> <span class="p">(</span><span class="nb">- </span><span class="nv">n</span> <span class="mi">1</span><span class="p">))))))</span>
<span class="p">(</span><span class="k">lambda </span><span class="p">(</span><span class="nf">fab</span><span class="p">)</span>
<span class="p">(</span><span class="k">lambda </span><span class="p">(</span><span class="nf">n</span><span class="p">)</span>
<span class="p">(</span><span class="k">if </span><span class="p">(</span><span class="nb">zero? </span><span class="nv">n</span><span class="p">)</span>
<span class="mi">1</span>
<span class="p">(</span><span class="nb">* </span><span class="nv">n</span> <span class="p">(</span><span class="nf">fab</span> <span class="p">(</span><span class="nb">- </span><span class="nv">n</span> <span class="mi">1</span><span class="p">)))))))</span>
</code></pre></div>
<p>看起来已经把自己传递给了自己,但马上发现<code>(fab (- n 1))</code>会失败,因为此时的<code>fab</code>不是一个阶乘函数,而是一个包含阶乘函数的函数,即要获取包含在内部的函数,因此调用方式要改成<code>((meta-fab meta-fab) (- n 1))</code>:</p>
<div class="highlight"><pre><code class="scheme language-scheme" data-lang="scheme"><span class="p">((</span><span class="k">lambda </span><span class="p">(</span><span class="nf">meta-fab</span><span class="p">)</span>
<span class="p">(</span><span class="k">lambda </span><span class="p">(</span><span class="nf">n</span><span class="p">)</span>
<span class="p">(</span><span class="k">if </span><span class="p">(</span><span class="nb">zero? </span><span class="nv">n</span><span class="p">)</span>
<span class="mi">1</span>
<span class="p">(</span><span class="nb">* </span><span class="nv">n</span> <span class="p">((</span><span class="nf">meta-fab</span> <span class="nv">meta-fab</span><span class="p">)</span> <span class="p">(</span><span class="nb">- </span><span class="nv">n</span> <span class="mi">1</span><span class="p">))))))</span>
<span class="p">(</span><span class="k">lambda </span><span class="p">(</span><span class="nf">meta-fab</span><span class="p">)</span>
<span class="p">(</span><span class="k">lambda </span><span class="p">(</span><span class="nf">n</span><span class="p">)</span>
<span class="p">(</span><span class="k">if </span><span class="p">(</span><span class="nb">zero? </span><span class="nv">n</span><span class="p">)</span>
<span class="mi">1</span>
<span class="p">(</span><span class="nb">* </span><span class="nv">n</span> <span class="p">((</span><span class="nf">meta-fab</span> <span class="nv">meta-fab</span><span class="p">)</span> <span class="p">(</span><span class="nb">- </span><span class="nv">n</span> <span class="mi">1</span><span class="p">)))))))</span>
</code></pre></div>
<p>把名字改成meta-fab就能清晰地看出它是阶乘的元函数,而不是阶乘函数本身。</p>
<h2>去除重复</h2>
<p>以上代码已经实现了lambda的自我调用,但其中包含重复的代码,meta-fab即做函数又做参数,即<code>(meta meta)</code>:</p>
<div class="highlight"><pre><code class="scheme language-scheme" data-lang="scheme"><span class="p">((</span><span class="k">lambda </span><span class="p">(</span><span class="nf">meta</span><span class="p">)</span>
<span class="p">(</span><span class="nf">meta</span> <span class="nv">meta</span><span class="p">))</span>
<span class="p">(</span><span class="k">lambda </span><span class="p">(</span><span class="nf">meta-fab</span><span class="p">)</span>
<span class="p">(</span><span class="k">lambda </span><span class="p">(</span><span class="nf">n</span><span class="p">)</span>
<span class="p">(</span><span class="k">if </span><span class="p">(</span><span class="nb">zero? </span><span class="nv">n</span><span class="p">)</span>
<span class="mi">1</span>
<span class="p">(</span><span class="nb">* </span><span class="nv">n</span> <span class="p">((</span><span class="nf">meta-fab</span> <span class="nv">meta-fab</span><span class="p">)</span> <span class="p">(</span><span class="nb">- </span><span class="nv">n</span> <span class="mi">1</span><span class="p">)))))))</span>
</code></pre></div>
<h2>提取阶乘函数</h2>
<p>因为我们想要的是阶乘函数,所以用fab取代<code>(meta-fab meta-fab)</code>,方法同样是使用参数列表命名:</p>
<div class="highlight"><pre><code class="scheme language-scheme" data-lang="scheme"><span class="p">((</span><span class="k">lambda </span><span class="p">(</span><span class="nf">meta</span><span class="p">)</span>
<span class="p">(</span><span class="nf">meta</span> <span class="nv">meta</span><span class="p">))</span>
<span class="p">(</span><span class="k">lambda </span><span class="p">(</span><span class="nf">meta-fab</span><span class="p">)</span>
<span class="p">((</span><span class="k">lambda </span><span class="p">(</span><span class="nf">fab</span><span class="p">)</span>
<span class="p">(</span><span class="k">lambda </span><span class="p">(</span><span class="nf">n</span><span class="p">)</span>
<span class="p">(</span><span class="k">if </span><span class="p">(</span><span class="nb">zero? </span><span class="nv">n</span><span class="p">)</span>
<span class="mi">1</span>
<span class="p">(</span><span class="nb">* </span><span class="nv">n</span> <span class="p">(</span><span class="nf">fab</span> <span class="p">(</span><span class="nb">- </span><span class="nv">n</span> <span class="mi">1</span><span class="p">))))))</span>
<span class="p">(</span><span class="nf">meta-fab</span> <span class="nv">meta-fab</span><span class="p">))))</span>
</code></pre></div>
<p>这段代码还不能正常运行,因为Scheme以及其他主流的编程语言实现都采用“应用序”,即执行函数时先计算参数的值,因此<code>(meta-fab meta-fab)</code>原来是在求阶乘的过程中才被执行,现在提取出来后执行的时间被提前,于是陷入无限循环。解决方法是把它包装在lambda中(你学到了lambda的另一个用处:延迟执行)。</p>
<div class="highlight"><pre><code class="scheme language-scheme" data-lang="scheme"><span class="p">((</span><span class="k">lambda </span><span class="p">(</span><span class="nf">meta</span><span class="p">)</span>
<span class="p">(</span><span class="nf">meta</span> <span class="nv">meta</span><span class="p">))</span>
<span class="p">(</span><span class="k">lambda </span><span class="p">(</span><span class="nf">meta-fab</span><span class="p">)</span>
<span class="p">((</span><span class="k">lambda </span><span class="p">(</span><span class="nf">fab</span><span class="p">)</span>
<span class="p">(</span><span class="k">lambda </span><span class="p">(</span><span class="nf">n</span><span class="p">)</span>
<span class="p">(</span><span class="k">if </span><span class="p">(</span><span class="nb">zero? </span><span class="nv">n</span><span class="p">)</span>
<span class="mi">1</span>
<span class="p">(</span><span class="nb">* </span><span class="nv">n</span> <span class="p">(</span><span class="nf">fab</span> <span class="p">(</span><span class="nb">- </span><span class="nv">n</span> <span class="mi">1</span><span class="p">))))))</span>
<span class="p">(</span><span class="k">lambda </span><span class="nv">args</span>
<span class="p">(</span><span class="nb">apply </span><span class="p">(</span><span class="nf">meta-fab</span> <span class="nv">meta-fab</span><span class="p">)</span> <span class="nv">args</span><span class="p">)))))</span>
</code></pre></div>
<p>此时,代码中第4行到第8行正是最初定义的匿名递归阶乘函数,我们终于得到了阶乘函数本身!</p>
<h2>形成模式</h2>
<p>如果把其中的阶乘函数作为一个整体提取出来,那就是得到一种“模式”,即能生成任意匿名递归函数的模式:</p>
<div class="highlight"><pre><code class="scheme language-scheme" data-lang="scheme"><span class="p">((</span><span class="k">lambda </span><span class="p">(</span><span class="nf">fn</span><span class="p">)</span>
<span class="p">((</span><span class="k">lambda </span><span class="p">(</span><span class="nf">meta</span><span class="p">)</span>
<span class="p">(</span><span class="nf">meta</span> <span class="nv">meta</span><span class="p">))</span>
<span class="p">(</span><span class="k">lambda </span><span class="p">(</span><span class="nf">meta-fn</span><span class="p">)</span>
<span class="p">(</span><span class="nf">fn</span>
<span class="p">(</span><span class="k">lambda </span><span class="nv">args</span>
<span class="p">(</span><span class="nb">apply </span><span class="p">(</span><span class="nf">meta-fn</span> <span class="nv">meta-fn</span><span class="p">)</span> <span class="nv">args</span><span class="p">))))))</span>
<span class="p">(</span><span class="k">lambda </span><span class="p">(</span><span class="nf">fab</span><span class="p">)</span>
<span class="p">(</span><span class="k">lambda </span><span class="p">(</span><span class="nf">n</span><span class="p">)</span>
<span class="p">(</span><span class="k">if </span><span class="p">(</span><span class="nb">zero? </span><span class="nv">n</span><span class="p">)</span>
<span class="mi">1</span>
<span class="p">(</span><span class="nb">* </span><span class="nv">n</span> <span class="p">(</span><span class="nf">fab</span> <span class="p">(</span><span class="nb">- </span><span class="nv">n</span> <span class="mi">1</span><span class="p">)))))))</span>
</code></pre></div>
<p>Lambda演算中称这个模式为Y组合子(Y-Combinator),即:</p>
<div class="highlight"><pre><code class="scheme language-scheme" data-lang="scheme"><span class="p">(</span><span class="k">define </span><span class="p">(</span><span class="nf">y-combinator</span> <span class="nv">fn</span><span class="p">)</span>
<span class="p">((</span><span class="k">lambda </span><span class="p">(</span><span class="nf">meta</span><span class="p">)</span>
<span class="p">(</span><span class="nf">meta</span> <span class="nv">meta</span><span class="p">))</span>
<span class="p">(</span><span class="k">lambda </span><span class="p">(</span><span class="nf">meta-fn</span><span class="p">)</span>
<span class="p">(</span><span class="nf">fn</span>
<span class="p">(</span><span class="k">lambda </span><span class="nv">args</span>
<span class="p">(</span><span class="nb">apply </span><span class="p">(</span><span class="nf">meta-fn</span> <span class="nv">meta-fn</span><span class="p">)</span> <span class="nv">args</span><span class="p">))))))</span>
</code></pre></div>
<p>有了Y组合子,我们就能定义任意的匿名递归函数。前文中定义的是递归求阶乘,再定义一个递归求斐波那契数:</p>
<div class="highlight"><pre><code class="scheme language-scheme" data-lang="scheme"><span class="p">(</span><span class="nf">y-combinator</span>
<span class="p">(</span><span class="k">lambda </span><span class="p">(</span><span class="nf">fib</span><span class="p">)</span>
<span class="p">(</span><span class="k">lambda </span><span class="p">(</span><span class="nf">n</span><span class="p">)</span>
<span class="p">(</span><span class="k">if </span><span class="p">(</span><span class="nb">< </span><span class="nv">n</span> <span class="mi">3</span><span class="p">)</span>
<span class="mi">1</span>
<span class="p">(</span><span class="nb">+ </span><span class="p">(</span><span class="nf">fib</span> <span class="p">(</span><span class="nb">- </span><span class="nv">n</span> <span class="mi">1</span><span class="p">))</span>
<span class="p">(</span><span class="nf">fib</span> <span class="p">(</span><span class="nb">- </span><span class="nv">n</span> <span class="mi">2</span><span class="p">)))))))</span>
</code></pre></div>
<h1>10种实现</h1>
<p>下面用10种不同的编程语言实现Y组合子,以及Y版的递归阶乘函数。实际开发中可能不会用上这样的技巧,但这些代码分别展示了这10种语言的诸多语法特性,能帮助你了解如何在这些语言中实现以下功能:</p>
<ol>
<li>如何定义匿名函数;</li>
<li>如何就地调用一个匿名函数;</li>
<li>如何将函数作为参数传递给其他函数;</li>
<li>如何定义参数数目不定的函数;</li>
<li>如何把函数作为值返回;</li>
<li>如何将数组里的元素平坦开来传递给函数;</li>
<li>三元表达式的使用方法。</li>
</ol>
<p>这十种编程语言,有Python、PHP、Perl、Ruby等大家耳熟能详的脚本语言,估计最让大家惊讶的应该是其中有Java!</p>
<h2>Scheme</h2>
<p>我始终觉得Scheme版是这么多种实现中最优雅的!它没有“刻意”的简洁,读起来很自然。</p>
<script src="https://gist.github.com/6445345.js?file=y-combinator.scm"> </script>
<h2>Clojure</h2>
<p>其实Clojure不需要借助Y-Combinator就能实现匿名递归函数,它的lambda——fn——支持传递一个函数名,为这个临时函数命名。也许Clojure的fn不应该叫匿名函数,应该叫临时函数更贴切。</p>
<p>同样是Lisp,Clojure版本比Scheme版本更简短,却让我感觉是一种刻意的简洁。我喜欢用fn取代lambda,但用稀奇古怪的符号来缩减代码量会让代码的可读性变差(我最近好像变得不太喜欢用符号,哈哈)。</p>
<script src="https://gist.github.com/6445345.js?file=y-combinator.clj"> </script>
<h2>Common Lisp</h2>
<p>Common Lisp版和Scheme版其实差不多,只不过Common Lisp属于Lisp-2,即函数命名空间与变量命名空间不同,因此调用匿名函数时需要额外的funcall。我个人不喜欢这个额外的调用,觉得它是冗余信息,位置信息已经包含了角色信息,就像命令行的第一个参数永远是命令。</p>
<script src="https://gist.github.com/6445345.js?file=y-combinator.lisp"> </script>
<h2>Ruby</h2>
<p>Ruby从Lisp那儿借鉴了许多,包括它的缺点。和Common Lisp一样,Ruby中执行一个匿名函数也需要额外的“.call”,或者使用中括号“<code>[]</code>”而不是和普通函数一样的小括号“<code>()</code>”,总之在Ruby中匿名函数与普通函数不一样!还有繁杂的符号也影响我在Ruby中使用匿名函数的心情,因此我会把Ruby看作语法更灵活、更简洁的Java,而不会考虑写函数式风格的代码。</p>
<script src="https://gist.github.com/6445345.js?file=y-combinator.rb"> </script>
<h2>Python</h2>
<p>Python中匿名函数的使用方式与普通函数一样,就这段代码而言,Python之于Ruby就像Scheme之于Common Lisp。但Python对Lambda的支持简直弱爆了,函数体只允许有一条语句!我决定我的工具箱中用Python取代C语言,虽然Python对匿名函数的支持只比C语言好一点点。</p>
<script src="https://gist.github.com/6445345.js?file=y-combinator.py"> </script>
<h2>Perl</h2>
<p>我个人对Perl函数不能声明参数的抱怨更甚于繁杂的符号!</p>
<script src="https://gist.github.com/6445345.js?file=y-combinator.pl"> </script>
<p>假设Perl能像其他语言一样声明参数列表,代码会更简洁直观:</p>
<div class="highlight"><pre><code class="perl language-perl" data-lang="perl"><span class="k">sub </span><span class="nf">y_combinator</span><span class="p">($f) {</span>
<span class="n">sub</span><span class="p">(</span><span class="nv">$u</span><span class="p">)</span> <span class="p">{</span> <span class="nv">$u</span><span class="o">-></span><span class="p">(</span><span class="nv">$u</span><span class="p">);</span> <span class="p">}</span><span class="o">-></span><span class="p">(</span><span class="n">sub</span><span class="p">(</span><span class="nv">$x</span><span class="p">)</span> <span class="p">{</span>
<span class="nv">$f</span><span class="o">-></span><span class="p">(</span><span class="k">sub </span><span class="p">{</span> <span class="nv">$x</span><span class="o">-></span><span class="p">(</span><span class="nv">$x</span><span class="p">)</span><span class="o">-></span><span class="p">(</span><span class="nv">@_</span><span class="p">);</span> <span class="p">});</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="k">print</span> <span class="n">y_combinator</span><span class="p">(</span><span class="n">sub</span><span class="p">(</span><span class="nv">$fab</span><span class="p">)</span> <span class="p">{</span>
<span class="n">sub</span><span class="p">(</span><span class="nv">$n</span><span class="p">)</span> <span class="p">{</span> <span class="nv">$n</span> <span class="o"><</span> <span class="mi">2</span><span class="p">?</span> <span class="mi">1</span><span class="p">:</span> <span class="nv">$n</span> <span class="o">*</span> <span class="nv">$fab</span><span class="o">-></span><span class="p">(</span><span class="nv">$n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">);</span> <span class="p">};</span>
<span class="p">})</span><span class="o">-></span><span class="p">(</span><span class="mi">10</span><span class="p">);</span>
</code></pre></div>
<h2>JavaScript</h2>
<p>JavaScript无疑是脚本语言中最流行的!但冗长的function、return等关键字总是刺痛我的神经:</p>
<script src="https://gist.github.com/6445345.js?file=y-combinator.pl"> </script>
<h2>Lua</h2>
<p>Lua和JavaScript有相同的毛病,最让我意外的是它没有三元运算符!不过没有使用花括号让代码看起来清爽不少~</p>
<script src="https://gist.github.com/6445345.js?file=y-combinator.lua"> </script>
<h2>PHP</h2>
<p>PHP也是JavaScript的难兄难弟,function、return……</p>
<p>此外,PHP版本是脚本语言中符号($、_、()、{})用的最多的!是的,比Perl还多。</p>
<script src="https://gist.github.com/6445345.js?file=y-combinator.pl"> </script>
<h2>Java</h2>
<p>最后,Java登场。我说的不是Java 8,即不是用Lambda表达式,而是匿名类!匿名函数的意义是把代码块作为参数传递,这正是匿名类所做得事情。</p>
<script src="https://gist.github.com/6445345.js?file=YCombinator.java"> </script>
http://zzp.me/2013-08-17/let-over-lambda/
你试过这样写C程序吗?
2013-08-17T15:50:00+08:00
2013-08-17T15:50:00+08:00
张泽鹏
http://zzp.me/
<h1>摘要</h1>
<p>面向对象风格和函数式编程风格是编写代码的两种风格,面向对象风格早为大众所认知,函数式风格也渐渐受到大家的关注。网上为其布道的文章不少,有人赞扬有人不屑,但鲜有展示一个完整例子的。例如很多人对函数式风格的印象只是“有人说它很好,但不清楚到底好在哪儿,更不知如何在实际的项目中获益”。</p>
<p>本文将采用C语言解决一个问题,围绕这个问题不断地变化需求、重构代码,分别展示两种风格如何从不同的侧面提高代码的可维护性。如果你没有耐心读完这篇长文章,可以参见:[附录II]直接看代码,但这篇文章会向你解释为什么代码会写成这样,以及写成这样的好处。</p>
<p>注:本文纯属个人观点,如有雷同,非常荣幸!</p>
<p>关键字:C语言; 结构化编程; 面向对象编程; 函数式编程</p>
<h1>什么是函数式风格?</h1>
<p>面向对象风格大家都耳熟能详,而提到函数式风格,脑海中或多或少会闪过一些耳熟能详的名词:无副作用、无状态、易于并行编程,甚至是Lisp那扭曲的前缀表达式。追根溯源,函数式风格源自λ演算:函数能作为值传递给其他函数或由其他函数返回。其中“函数”是一种抽象的概念,可以理解成代码块,在C语言里叫函数或过程,在Java中叫成员方法……因此,函数式风格的本质是函数作为“第一等公民”。在我看来,诸如闭包、匿名函数等特性仅是添头,例如Emacs Lisp最初不支持闭包,但不影响它是一门支持函数式风格的编程语言。</p>
<p>有些人会把函数式风格与面向对象风格对立起来,但在我看来这两种风格都是为了提高代码的可维护性,可以相辅相成:</p>
<ul>
<li>函数式风格重点是增强类型系统:一些编程语言提供的基础数据类型仅有数值型和字符串型,函数式风格要求函数也是基础数据类型,即代码也是一种数据;</li>
<li>面向对象风格侧重代码的组织形式:要求把数据和操作这些数据的函数组织在同一个类中,提高内聚;对象之间通过调用开放的接口通讯,降低耦合。</li>
</ul>
<h1>代码即数据的作用?</h1>
<p>使用不支持函数式风格的编程语言开发,将迫使我们永远在语言恰好提供的基础功能上工作。例如迭代只能使用for、while等关键字;读写文件每次都要写fopen、fclose;并行加锁也少不了lock和unlock。面对这些大同小异的冗余代码总会很无奈:如果XX语言能提供XX特性该多好啊!</p>
<p>代码即数据让这一切成为可能,它允许你自定义控制语句。如果语言不支持某个期望的特性,那就自己动手加一个!后文将展示如何自定义控制语句,以及它如何提高代码的可维护性。</p>
<h1>为什么选C语言?</h1>
<p>函数若要作为“第一等公民”,至少需要满足以下四条特权:</p>
<ol>
<li>可以用变量命名;</li>
<li>可以提供给过程作为参数;</li>
<li>可以由过程作为结果返回;</li>
<li>可以包含在数据结构中。</li>
</ol>
<p>对照之下会惊讶地发现,C这门看似离函数式风格最远的编程语言居然也符合上述条件;此外,相比其他对函数式风格支持更好的语言(如Lisp、Haskell等),至少C的语法不那么古怪;何况熟悉C语系(如Java、C#等)语法的同学也更多,方便大家用自己熟悉的语言实践。</p>
<h1>问题描述</h1>
<p>作为贯穿全文的主线,这有一个问题需要你开发一个C程序来完成任务:有一个存有职员信息(姓名、年龄、工资)的文件“work.txt”,内容如下:</p>
<div class="highlight"><pre><code class="text language-text" data-lang="text">William 35 25000
Kishore 41 35000
Wallace 37 20000
Bruce 39 15000
</code></pre></div>
<ol>
<li>要求从文件中读取这些信息,并输出到屏幕上;</li>
<li>为所有工资小于三万的员工涨3000元;</li>
<li>在屏幕上输出薪资调整后的结果;</li>
<li>把调整后的结果保存到原始文件。</li>
</ol>
<p>即运行的结果是屏幕上要有八行输出,“work.txt”的内容将变成:</p>
<div class="highlight"><pre><code class="text language-text" data-lang="text">William 35 28000
Kishore 41 35000
Wallace 37 23000
Bruce 39 18000
</code></pre></div>
<h1>快速实现</h1>
<p>这个问题很简单,简单到把所有代码都塞到main函数里也不觉得太长:</p>
<div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="cp">#include <stdio.h></span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
<span class="k">struct</span> <span class="p">{</span>
<span class="kt">char</span> <span class="n">name</span><span class="p">[</span><span class="mi">8</span><span class="p">];</span>
<span class="kt">int</span> <span class="n">age</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">salary</span><span class="p">;</span>
<span class="p">}</span> <span class="n">e</span><span class="p">[</span><span class="mi">4</span><span class="p">];</span>
<span class="kt">FILE</span> <span class="o">*</span><span class="n">istream</span><span class="p">,</span> <span class="o">*</span><span class="n">ostream</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">i</span><span class="p">,</span> <span class="n">length</span><span class="p">;</span>
<span class="n">istream</span> <span class="o">=</span> <span class="n">fopen</span><span class="p">(</span><span class="s">"work.txt"</span><span class="p">,</span> <span class="s">"r"</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">fscanf</span><span class="p">(</span><span class="n">istream</span><span class="p">,</span> <span class="s">"%s%d%d"</span><span class="p">,</span> <span class="n">e</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">name</span><span class="p">,</span> <span class="o">&</span><span class="n">e</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">age</span><span class="p">,</span> <span class="o">&</span><span class="n">e</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">salary</span><span class="p">)</span> <span class="o">==</span> <span class="mi">3</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"%s %d %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">e</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">name</span><span class="p">,</span> <span class="n">e</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">age</span><span class="p">,</span> <span class="n">e</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">salary</span><span class="p">);</span>
<span class="n">length</span> <span class="o">=</span> <span class="n">i</span><span class="p">;</span>
<span class="n">fclose</span><span class="p">(</span><span class="n">istream</span><span class="p">);</span>
<span class="n">ostream</span> <span class="o">=</span> <span class="n">fopen</span><span class="p">(</span><span class="s">"work.txt"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">length</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">e</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">salary</span> <span class="o"><</span> <span class="mi">30000</span><span class="p">)</span>
<span class="n">e</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">salary</span> <span class="o">+=</span> <span class="mi">3000</span><span class="p">;</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"%s %d %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">e</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">name</span><span class="p">,</span> <span class="n">e</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">age</span><span class="p">,</span> <span class="n">e</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">salary</span><span class="p">);</span>
<span class="n">fprintf</span><span class="p">(</span><span class="n">ostream</span><span class="p">,</span> <span class="s">"%s %d %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">e</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">name</span><span class="p">,</span> <span class="n">e</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">age</span><span class="p">,</span> <span class="n">e</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">salary</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">fclose</span><span class="p">(</span><span class="n">ostream</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>其中第一个循环不断地从work.txt中读数据,直到文件末尾,同时把信息输出到屏幕,即实现了需求#1;第二个循环遍历所有数据,为薪资小于三万的职员增加三千元(需求#2),并把调整后的结果输出屏幕(需求#3)和work.txt中(需求#4)。</p>
<h1>当变化来临时</h1>
<p>上面的代码简洁明了,而且运行良好,作为应付无需维护、需求亦不会变化的课后作业绰绰有余。可惜,我们没有活在新闻联播里,需求总在不断地变化,以至于要不停地维护代码。下面从维护的角度罗列几个问题,并尝试重构。</p>
<h2>当文件打开失败时</h2>
<p>程序发布之后,就面临各种苛刻的运行环境,例如文件<code>work.txt</code>可能没有读或写权限。代码的维护者需要通过错误日志里的信息定位出错的位置,但不是所有环境都会提供充足的信息,例如<code>Linux</code>下,没有读或写权限都只输出“<code>Segmentation fault</code>”,仅凭这段错误信息无法确定是哪一句<code>fopen</code>出错。</p>
<h2>当职员信息数量变化时</h2>
<p>样例中只有4条记录,不意味着真实环境中永远只有4条记录,甚至可以认为记录的数目是不确定的。臆断结构体数组的最大长度是4或其他数值都是不合适的,需要能自适应不同的数目。</p>
<h2>当字段类型变化时</h2>
<p>虽然样例中工资都是整数,但真实环境中工资很可能是浮点数。把<code>int salary</code>改成<code>float salary</code>意味着所有涉及输入输出的地方都要修改:<code>%d</code>换成<code>%f</code>。</p>
<p>在短短不到30行的代码里尚且有4处需要修改,换成庞大的项目,维护成本将不可估量。</p>
<h2>当字段数目变化时</h2>
<p>客户提出职员信息中需新增一列,保存员工入职的年份。这带来的影响和上个问题一样。</p>
<h2>当业务逻辑变化时</h2>
<p>本例的业务逻辑就是调薪和输出,几乎都集中在了第二个循环体中。如果不断地增加新的业务逻辑,循环体就会爆炸式地增长。而且业务逻辑可能需要相互组合,代码就变得杂乱无章。</p>
<h1>面向对象风格</h1>
<p>上节提到的变化都很常见,你肯定还能想出更多。它们综合的维护成本已不比完全重写低,即代码应对合理需求变化的能力差,可维护性低。究其原因,是相同或相似的代码散落在多处,因此一个变化就引起多处更改,误改或漏改都在所难免。</p>
<p>回顾前文面向对象风格的宗旨:把数据和操作数据的函数集中在一起,开放操作数据的接口供其它对象或方法调用。这恰好能解决把操作数据的方法散落在各处的问题,下面就用面向对象的思想重构代码。</p>
<h2>抽象数据结构</h2>
<p>首先需要抽象出要处理的对象类型,此处为结构体命名即可:</p>
<div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="k">typedef</span> <span class="k">struct</span> <span class="n">_Employee</span> <span class="p">{</span>
<span class="p">...</span>
<span class="p">}</span> <span class="o">*</span><span class="n">Employee</span><span class="p">;</span>
</code></pre></div>
<p>需要注意的是,<code>Employee</code>是结构体<code>_Employee</code>的指针,因为操作结构体,使用指针比直接使用对象更频繁。</p>
<p>接着要选择一种数据结构作为容器,由于数据是一组个数不确定的线性结构,单链表正好适合这样的场景:</p>
<div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="k">typedef</span> <span class="k">struct</span> <span class="n">_Employee</span> <span class="p">{</span>
<span class="n">String</span> <span class="n">name</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">age</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">salary</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">_Employee</span> <span class="o">*</span><span class="n">next</span><span class="p">;</span>
<span class="p">}</span> <span class="o">*</span><span class="n">Employee</span><span class="p">;</span>
</code></pre></div>
<h2>开放接口</h2>
<p>根据需求,职员对象至少提供从文件中读取信息、输出到屏幕、保存到文件、调整薪资四项功能。其中输出到屏幕和保存到文件可以合并成输出到输出流中,因此它将开放以下四个接口:</p>
<ol>
<li><code>employee_read</code>:批量从输入流中读取职员信息并返回</li>
<li><code>employee_free</code>:批量释放动态申请的空间</li>
<li><code>employee_print</code>:批量输出职员信息到输出流</li>
<li><code>employee_adjust_salary</code>:遍历职员信息并调整薪资</li>
</ol>
<h2>构造函数</h2>
<p>即创建并初始化对象的函数。</p>
<div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="k">static</span> <span class="n">Employee</span> <span class="nf">employee_read_node</span><span class="p">(</span><span class="n">File</span> <span class="n">istream</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Employee</span> <span class="n">e</span> <span class="o">=</span> <span class="p">(</span><span class="n">Employee</span><span class="p">)</span><span class="n">calloc</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">_Employee</span><span class="p">));</span>
<span class="k">if</span> <span class="p">(</span><span class="n">e</span> <span class="o">!=</span> <span class="nb">NULL</span> <span class="o">&&</span> <span class="n">fscanf</span><span class="p">(</span><span class="n">istream</span><span class="p">,</span> <span class="s">"%s%d%d"</span><span class="p">,</span> <span class="n">e</span><span class="o">-></span><span class="n">name</span><span class="p">,</span> <span class="o">&</span><span class="n">e</span><span class="o">-></span><span class="n">age</span><span class="p">,</span> <span class="o">&</span><span class="n">e</span><span class="o">-></span><span class="n">salary</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">3</span><span class="p">)</span> <span class="p">{</span>
<span class="n">employee_free</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
<span class="n">e</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">e</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>构造函数先通过<code>calloc</code>申请了一片内存空间(并自动初始化为0),再从给定的输入流中读取职员信息来初始化对象,如果输入流中没有更多的数据,就释放空间并返回空指针。</p>
<p>该函数只能构造单个对象,而文件中有一组对象,且需要串联成单链表结构,因此接口<code>employee_read</code>的工作就是组织这些对象:</p>
<div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="n">Employee</span> <span class="nf">employee_read</span><span class="p">(</span><span class="n">File</span> <span class="n">istream</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Employee</span> <span class="n">e</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">head</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">tail</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span><span class="n">e</span> <span class="o">=</span> <span class="n">employee_read_node</span><span class="p">(</span><span class="n">istream</span><span class="p">))</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">head</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
<span class="n">tail</span><span class="o">-></span><span class="n">next</span> <span class="o">=</span> <span class="n">e</span><span class="p">;</span>
<span class="n">tail</span> <span class="o">=</span> <span class="n">e</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="n">head</span> <span class="o">=</span> <span class="n">tail</span> <span class="o">=</span> <span class="n">e</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">head</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>由于<code>employee_read_node</code>是一个辅助函数,不是对外开放的接口,所以使用<code>static</code>修饰把作用域限定在当前文件。</p>
<h2>析构函数</h2>
<p>因为对象的空间是动态申请的,需要提供手工释放的析构函数,即<code>employee_free</code>:</p>
<div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="kt">void</span> <span class="nf">employee_free</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Employee</span> <span class="n">p</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span><span class="n">p</span> <span class="o">=</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="n">e</span> <span class="o">=</span> <span class="n">e</span><span class="o">-></span><span class="n">next</span><span class="p">;</span>
<span class="n">free</span><span class="p">(</span><span class="n">p</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<h2>输出</h2>
<p>如果说输入是把字符串反序列化成对象的过程,那输出就是输入的逆运算,即把对象序列化成字符串的过程。因此,输出的要求是格式必须和输入文件保持一致,允许程序多次处理。此处的输出就是遍历整个集合并输出到输出流:</p>
<div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="kt">void</span> <span class="nf">employee_print</span><span class="p">(</span><span class="n">File</span> <span class="n">ostream</span><span class="p">,</span> <span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(;</span> <span class="n">e</span><span class="p">;</span> <span class="n">e</span> <span class="o">=</span> <span class="n">e</span><span class="o">-></span><span class="n">next</span><span class="p">)</span> <span class="p">{</span>
<span class="n">fprintf</span><span class="p">(</span><span class="n">ostream</span><span class="p">,</span> <span class="s">"%s %d %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">e</span><span class="o">-></span><span class="n">name</span><span class="p">,</span> <span class="n">e</span><span class="o">-></span><span class="n">age</span><span class="p">,</span> <span class="n">e</span><span class="o">-></span><span class="n">salary</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<h2>核心业务逻辑:调整薪资</h2>
<p>与输出类似,调整薪资也是遍历整个集合,为符合要求的职员调薪:</p>
<div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="kt">void</span> <span class="nf">employee_adjust_salary</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(;</span> <span class="n">e</span><span class="p">;</span> <span class="n">e</span> <span class="o">=</span> <span class="n">e</span><span class="o">-></span><span class="n">next</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">e</span><span class="o">-></span><span class="n">salary</span> <span class="o"><</span> <span class="mi">30000</span><span class="p">)</span> <span class="p">{</span>
<span class="n">e</span><span class="o">-></span><span class="n">salary</span> <span class="o">+=</span> <span class="mi">3000</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<h2>解决问题</h2>
<p>经过以上几个步骤,为职员信息管理这个领域定义了一套方便的接口。此时的<code>main</code>函数不用再操心数据具体以什么形式组织、如何获取、如何输出,只需向<code>Employee</code>对象发送消息(调用接口)即可完成任务。</p>
<div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
<span class="n">File</span> <span class="n">istream</span><span class="p">,</span> <span class="n">ostream</span><span class="p">;</span>
<span class="n">Employee</span> <span class="n">e</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="n">istream</span> <span class="o">=</span> <span class="n">fopen</span><span class="p">(</span><span class="s">"work.txt"</span><span class="p">,</span> <span class="s">"r"</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">istream</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
<span class="n">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">"Cannot open work.txt with r mode.</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="n">exit</span><span class="p">(</span><span class="n">EXIT_FAILURE</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">e</span> <span class="o">=</span> <span class="n">employee_read</span><span class="p">(</span><span class="n">istream</span><span class="p">);</span>
<span class="n">fclose</span><span class="p">(</span><span class="n">istream</span><span class="p">);</span>
<span class="n">employee_print</span><span class="p">(</span><span class="n">stdout</span><span class="p">,</span> <span class="n">e</span><span class="p">);</span>
<span class="n">employee_adjust_salary</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
<span class="n">employee_print</span><span class="p">(</span><span class="n">stdout</span><span class="p">,</span> <span class="n">e</span><span class="p">);</span>
<span class="n">ostream</span> <span class="o">=</span> <span class="n">fopen</span><span class="p">(</span><span class="s">"work.txt"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">ostream</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
<span class="n">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">"Cannot open work.txt with w mode.</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="n">exit</span><span class="p">(</span><span class="n">EXIT_FAILURE</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">employee_print</span><span class="p">(</span><span class="n">ostream</span><span class="p">,</span> <span class="n">e</span><span class="p">);</span>
<span class="n">fclose</span><span class="p">(</span><span class="n">ostream</span><span class="p">);</span>
<span class="n">employee_free</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
<span class="k">return</span> <span class="n">EXIT_SUCCESS</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>重构后的代码与需求的描述更接近,虽然代码量膨胀了三倍,但能解决前文的问题:</p>
<ol>
<li>文件指针做空指针检查</li>
<li>单链表容量能自动扩展</li>
<li>字段类型或数目变化时仅修改输入和输出两处</li>
<li>每项业务逻辑为独立的函数,易扩展且组合灵活</li>
</ol>
<p>完整的代码请参见:[附录I]</p>
<h1>欢迎变化再次光临</h1>
<p>经过重构的代码可维护性更好,因为每个函数的职责是单一的:</p>
<ol>
<li><code>employee_read_node</code>:应对输入源的变化,如列的顺序改变;</li>
<li><code>employee_read</code>:应对集合结构的变化,如单向链表改成双向链表;</li>
<li><code>employee_print</code>:应对输出格式的变化,如输出成CSV结构;</li>
<li><code>employee_adjust_salary</code>:应对业务逻辑的变化,如调薪幅度增大。</li>
</ol>
<p>不过,代码仍有不少重复之处,“重复”是维护性的大敌。想想你会如何应付下面这些问题?</p>
<h2>数据源升级</h2>
<p>上游系统在升级后,work.txt的第一行提供了行数:</p>
<div class="highlight"><pre><code class="text language-text" data-lang="text">4
William 35 25000
Kishore 41 35000
Wallace 37 20000
Bruce 39 15000
</code></pre></div>
<p>而且,原系统频繁地申请空间也影响到性能。经过权衡,决定用数组取代单链表,这样只需一次性申请足够大的空间。</p>
<p>凭借面向对象风格的优势,对接口的实现的修改不会影响接口的使用,因此<code>main</code>函数无需任何修改。但对<code>Employee</code>对象而言却是灾难:每个接口的实现都与内部数据结构紧紧地绑在一起。几乎所有实现里都用<code>for</code>或<code>while</code>循环遍历整个链表,底层数据结构的变化意味着遍历方式的变化,即所有接口的实现全部需要重写!</p>
<h2>优雅的访问文件</h2>
<p>但凡涉及访问文件的代码,都需要<code>fopen</code>、检查文件指针、存取数据、<code>fclose</code>,这几乎成了一种魔咒。比如<code>main</code>函数中,建立文件访问上下文的代码占去近一半的代码量。考虑规避这种魔咒,自动管理文件资源,在操作完成后自动关闭。</p>
<h1>函数式风格</h1>
<p>以上两个需求又足以让整个工程推倒重来。需求#1要求抽象出遍历集合的方法,在迭代的过程中执行各自的循环体处理数据;需求#2则要创建一种上下文,能自动打开文件,在执行访问操作完成后自动关闭。它们都涉及将代码块作为函数参数,在某个时刻调用,这正是函数式风格擅长的领域。</p>
<p>C语言中,函数指针类型的变量可以指向参数类型与返回值类型都兼容的函数。虽然C不允许嵌套地定义函数或定义匿名函数,但确实允许将函数作为值传递,例如<code>qsort</code>的比较函数。</p>
<h2>自定义遍历语句</h2>
<p>先试着从<code>employee_print</code>和<code>employee_adjust_salary</code>中抽象出迭代过程:</p>
<div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="k">typedef</span> <span class="nf">void</span> <span class="p">(</span><span class="o">*</span><span class="n">Callback</span><span class="p">)(</span><span class="n">Employee</span><span class="p">);</span>
<span class="kt">void</span> <span class="nf">foreach</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">,</span> <span class="n">Callback</span> <span class="n">fn</span><span class="p">)</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(;</span> <span class="n">e</span><span class="p">;</span> <span class="n">e</span> <span class="o">=</span> <span class="n">e</span><span class="o">-></span><span class="n">next</span><span class="p">)</span> <span class="p">{</span>
<span class="n">fn</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>其中<code>Callback</code>是自定义的函数指针类型,能接收一个<code>Employee</code>类型的参数,并且无返回值。</p>
<p>上述过程照搬了两个函数中相同的代码,但作为通用的迭代方法,这个实现有一个<code>bug</code>:当<code>fn</code>的调用破坏了<code>e->next</code>的值时(例如调用<code>free</code>),<code>e = e->next</code>的值就变得未知。为了规避这个问题,需引入一个额外的变量:</p>
<div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="kt">void</span> <span class="nf">foreach</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">,</span> <span class="n">Callback</span> <span class="n">fn</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Employee</span> <span class="n">p</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span><span class="n">p</span> <span class="o">=</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="n">e</span> <span class="o">=</span> <span class="n">e</span><span class="o">-></span><span class="n">next</span><span class="p">;</span>
<span class="n">fn</span><span class="p">(</span><span class="n">p</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>在<code>fn</code>破坏节点内容前先获得<code>next</code>节点的引用,这样就能避免<code>free</code>这样具破坏性的过程影响遍历。使用这个自定义的控制语句(或高阶函数)重构<code>employee_free</code>函数,让它从遍历的细节中解放:</p>
<div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="k">static</span> <span class="kt">void</span> <span class="nf">employee_free_node</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">e</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
<span class="n">free</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="nf">employee_free</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="n">foreach</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="n">employee_free_node</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>
<p>由于C不支持定义匿名函数,因此不得不定义一个释放单个节点的辅助函数。重构<code>employee_adjust_salary</code>与此类似:</p>
<div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="k">static</span> <span class="kt">void</span> <span class="nf">employee_adjust_salary_node</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">e</span><span class="o">-></span><span class="n">salary</span> <span class="o"><</span> <span class="mi">30000</span><span class="p">)</span> <span class="p">{</span>
<span class="n">e</span><span class="o">-></span><span class="n">salary</span> <span class="o">+=</span> <span class="mi">3000</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="nf">employee_adjust_salary</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="n">foreach</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="n">employee_adjust_salary_node</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>
<h2>文件访问上下文</h2>
<p>但是,重构<code>employee_print</code>的过程遇到了障碍:它需要一个额外的输出流,造成接口与<code>Callback</code>不兼容。似乎只能再为<code>IO</code>接口额外定义能接收两个参数的<code>IoCallback</code>接口,但如此一来又不得不实现一套专门处理它的<code>io_foreach</code>,这是无法接受的!其实,利用偏函数技术能很优雅地解决这个问题,可惜C语言不允许定义匿名函数,也不支持闭包,只能感叹:臣妾做不到啊!由此可见匿名函数与闭包对函数式风格的友好性。</p>
<p>经过深思熟虑,我做出了一个很艰难地决定:使用<code>freopen</code>重定向标准输入输出流。这就能使用<code>printf</code>输出,无需提供额外的文件流。</p>
<div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="kt">void</span> <span class="nf">employee_print</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(;</span> <span class="n">e</span><span class="p">;</span> <span class="n">e</span> <span class="o">=</span> <span class="n">e</span><span class="o">-></span><span class="n">next</span><span class="p">)</span> <span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"%s %d %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">e</span><span class="o">-></span><span class="n">name</span><span class="p">,</span> <span class="n">e</span><span class="o">-></span><span class="n">age</span><span class="p">,</span> <span class="n">e</span><span class="o">-></span><span class="n">salary</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">...</span>
<span class="n">ostream</span> <span class="o">=</span> <span class="n">freopen</span><span class="p">(</span><span class="s">"work.txt"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">,</span> <span class="n">stdout</span><span class="p">);</span>
<span class="p">...</span>
<span class="n">employee_print</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
</code></pre></div>
<p>如此,<code>employee_print</code>的接口与<code>Callback</code>也兼容了,可以使用<code>foreach</code>来重构:</p>
<div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="k">static</span> <span class="kt">void</span> <span class="nf">employee_print_node</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"%s %d %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">e</span><span class="o">-></span><span class="n">name</span><span class="p">,</span> <span class="n">e</span><span class="o">-></span><span class="n">age</span><span class="p">,</span> <span class="n">e</span><span class="o">-></span><span class="n">salary</span><span class="p">);</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="nf">employee_print</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="n">foreach</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="n">employee_print_node</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>
<p>有了以上基础,创建上下文的方法就呼之欲出了:</p>
<div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="kt">void</span> <span class="nf">with_open_file</span><span class="p">(</span><span class="n">String</span> <span class="n">filename</span><span class="p">,</span> <span class="n">String</span> <span class="n">mode</span><span class="p">,</span> <span class="n">Callback</span> <span class="n">fn</span><span class="p">,</span> <span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="n">File</span> <span class="n">file</span> <span class="o">=</span> <span class="n">freopen</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="n">mode</span><span class="p">,</span> <span class="p">(</span><span class="n">mode</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="sc">'r'</span><span class="o">?</span> <span class="n">stdin</span><span class="o">:</span> <span class="n">stdout</span><span class="p">));</span>
<span class="k">if</span> <span class="p">(</span><span class="n">file</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
<span class="n">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">"Cannot open %s with %s mode.</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">filename</span><span class="p">,</span> <span class="n">mode</span><span class="p">);</span>
<span class="n">exit</span><span class="p">(</span><span class="n">EXIT_FAILURE</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">fn</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
<span class="n">fclose</span><span class="p">(</span><span class="n">file</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>
<p>先把重定文件流到标准输入或输出流;执行回调函数;关闭文件流。如此,保存数据到文件的代码将简化成一句话:</p>
<div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="n">with_open_file</span><span class="p">(</span><span class="s">"work.txt"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">,</span> <span class="n">employee_print</span><span class="p">,</span> <span class="n">e</span><span class="p">);</span>
</code></pre></div>
<p>对我而言,这样的代码很优雅,我迫不及待地希望<code>employee_read</code>也支持这种方式!</p>
<p>但<code>employee_read</code>又是一块难啃的骨头:它不仅参数类型与<code>Callback</code>不兼容,连返回值类型也不同。为将返回值重构成<code>void</code>,不得不提供一个额外参数保存返回值,并且类型是<code>Employee*</code>:</p>
<div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="k">static</span> <span class="kt">void</span> <span class="nf">employee_read_node</span><span class="p">(</span><span class="n">File</span> <span class="n">istream</span><span class="p">,</span> <span class="n">Employee</span><span class="o">*</span> <span class="n">head</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Employee</span> <span class="n">e</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="n">e</span> <span class="o">=</span> <span class="o">*</span><span class="n">head</span> <span class="o">=</span> <span class="p">(</span><span class="n">Employee</span><span class="p">)</span><span class="n">calloc</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">_Employee</span><span class="p">));</span>
<span class="k">if</span> <span class="p">(</span><span class="n">e</span> <span class="o">!=</span> <span class="nb">NULL</span> <span class="o">&&</span> <span class="n">fscanf</span><span class="p">(</span><span class="n">istream</span><span class="p">,</span> <span class="s">"%s%d%d"</span><span class="p">,</span> <span class="n">e</span><span class="o">-></span><span class="n">name</span><span class="p">,</span> <span class="o">&</span><span class="n">e</span><span class="o">-></span><span class="n">age</span><span class="p">,</span> <span class="o">&</span><span class="n">e</span><span class="o">-></span><span class="n">salary</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">3</span><span class="p">)</span> <span class="p">{</span>
<span class="n">employee_free</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
<span class="o">*</span><span class="n">head</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="nf">employee_read</span><span class="p">(</span><span class="n">File</span> <span class="n">istream</span><span class="p">,</span> <span class="n">Employee</span><span class="o">*</span> <span class="n">head</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Employee</span> <span class="n">e</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">tail</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="o">*</span><span class="n">head</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span><span class="n">employee_read_node</span><span class="p">(</span><span class="n">istream</span><span class="p">,</span> <span class="o">&</span><span class="n">e</span><span class="p">),</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">*</span><span class="n">head</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
<span class="n">tail</span><span class="o">-></span><span class="n">next</span> <span class="o">=</span> <span class="n">e</span><span class="p">;</span>
<span class="n">tail</span> <span class="o">=</span> <span class="n">e</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="o">*</span><span class="n">head</span> <span class="o">=</span> <span class="n">tail</span> <span class="o">=</span> <span class="n">e</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>看起来这样就可以使用与<code>employee_print</code>相同的技巧去除<code>istream</code>这个参数了。其实不然,<code>Callback</code>的参数是<code>Employee</code>,不是<code>Employee*</code>,接口依旧不兼容。这也是静态类型对函数式风格不友好的一个例子,静态类型在编译期就确定变量的类型,限制越多则灵活性越差,相应的受众面也越窄。</p>
<p>当然,C语言也提供了一个替代方案,使用万能指针<code>void*</code>作为<code>Callback</code>的参数(例如<code>qsort</code>就是这么做的)。但这样做,要么所有实现都要改成<code>void*</code>,然后在函数里使用强制转换;要么得忍受编译器一堆类型不匹配的<code>warning</code>。权衡再三,还是决定让<code>employee_read</code>牺牲小我,<code>Callback</code>接口继续使用<code>Employee</code>做参数类型,在<code>employee_read</code>中将参数类型强制转换成<code>Employee*</code>。</p>
<div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="k">static</span> <span class="kt">void</span> <span class="nf">employee_read_node</span><span class="p">(</span><span class="n">Employee</span> <span class="n">node</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Employee</span> <span class="n">e</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">,</span> <span class="o">*</span><span class="n">head</span> <span class="o">=</span> <span class="p">(</span><span class="n">Employee</span><span class="o">*</span><span class="p">)</span> <span class="n">node</span><span class="p">;</span>
<span class="n">e</span> <span class="o">=</span> <span class="o">*</span><span class="n">head</span> <span class="o">=</span> <span class="p">(</span><span class="n">Employee</span><span class="p">)</span><span class="n">calloc</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">_Employee</span><span class="p">));</span>
<span class="p">...</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="nf">employee_read</span><span class="p">(</span><span class="n">Employee</span> <span class="n">list</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Employee</span> <span class="n">e</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">,</span> <span class="o">*</span><span class="n">head</span> <span class="o">=</span> <span class="p">(</span><span class="n">Employee</span><span class="o">*</span><span class="p">)</span> <span class="n">list</span><span class="p">,</span> <span class="n">tail</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="o">*</span><span class="n">head</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span><span class="n">employee_read_node</span><span class="p">((</span><span class="n">Employee</span><span class="p">)</span><span class="o">&</span><span class="n">e</span><span class="p">),</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="p">...</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<h2>解决问题</h2>
<p>重构后的主函数变得愈加简洁,没有啰嗦的文件操作,甚至可以看成描述原始需求的伪代码。</p>
<div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Employee</span> <span class="n">e</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="n">with_open_file</span><span class="p">(</span><span class="s">"work.txt"</span><span class="p">,</span> <span class="s">"r"</span><span class="p">,</span> <span class="n">employee_read</span><span class="p">,</span> <span class="p">(</span><span class="n">Employee</span><span class="p">)</span><span class="o">&</span><span class="n">e</span><span class="p">);</span>
<span class="n">employee_print</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
<span class="n">employee_adjust_salary</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
<span class="n">employee_print</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
<span class="n">with_open_file</span><span class="p">(</span><span class="s">"work.txt"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">,</span> <span class="n">employee_print</span><span class="p">,</span> <span class="n">e</span><span class="p">);</span>
<span class="n">employee_free</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
<span class="k">return</span> <span class="n">EXIT_SUCCESS</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<p>重构后的完整代码请参见:[附录II]。回到需求#1,切换数据结构。从单链表切换成数组,结构体需把“<code>struct _Employee *next</code>”替换成“<code>int length</code>”。而用于创建集合的<code>employee_read</code>更是责不旁贷:</p>
<div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="kt">void</span> <span class="nf">employee_read</span><span class="p">(</span><span class="n">Employee</span> <span class="n">list</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Employee</span> <span class="n">e</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">size</span><span class="p">;</span>
<span class="n">scanf</span><span class="p">(</span><span class="s">"%d"</span><span class="p">,</span> <span class="o">&</span><span class="n">size</span><span class="p">);</span>
<span class="o">*</span><span class="p">((</span><span class="n">Employee</span><span class="o">*</span><span class="p">)</span> <span class="n">list</span><span class="p">)</span> <span class="o">=</span> <span class="n">e</span> <span class="o">=</span> <span class="p">(</span><span class="n">Employee</span><span class="p">)</span><span class="n">calloc</span><span class="p">(</span><span class="n">size</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">_Employee</span><span class="p">));</span>
<span class="n">e</span><span class="o">-></span><span class="n">length</span> <span class="o">=</span> <span class="n">size</span><span class="p">;</span>
<span class="n">foreach</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="n">employee_read_node</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>
<p>与之前逐个为对象申请不同,现在一次性申请,即简化了代码又提高了性能。由于内存申请被转移出,<code>employee_read_node</code>也得到极大的简化:</p>
<div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="kt">void</span> <span class="nf">employee_read_node</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="n">scanf</span><span class="p">(</span><span class="s">"%s%d%d"</span><span class="p">,</span> <span class="n">e</span><span class="o">-></span><span class="n">name</span><span class="p">,</span> <span class="o">&</span><span class="n">e</span><span class="o">-></span><span class="n">age</span><span class="p">,</span> <span class="o">&</span><span class="n">e</span><span class="o">-></span><span class="n">salary</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>
<p>因为空间已提前申请好,因此无需传入指针。伴随空间申请方式的改变,空间释放的方式也要相应调整:</p>
<div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="kt">void</span> <span class="nf">employee_free</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="n">free</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>
<p>不再需要辅助函数<code>employee_free_node</code>。接着是遍历:</p>
<div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="kt">void</span> <span class="nf">foreach</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">,</span> <span class="n">Callback</span> <span class="n">fn</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">int</span> <span class="n">i</span><span class="p">,</span> <span class="n">length</span> <span class="o">=</span> <span class="n">e</span><span class="o">-></span><span class="n">length</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">length</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">fn</span><span class="p">(</span><span class="n">e</span><span class="o">++</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>同样需要额外的变量length保存最初长度的信息。最后,还有一个可能意想不到的改动——<code>employee_print</code>。前文提过,“输出”是“输入”的逆操作,它的职责除了展示和保存数据,还要保持格式与输入兼容,即输出的数据还能再次被输入处理。因此需要在开头输出行数:</p>
<div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="kt">void</span> <span class="nf">employee_print</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"%d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">e</span><span class="o">-></span><span class="n">length</span><span class="p">);</span>
<span class="n">foreach</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="n">employee_print_node</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>
<p>修改后的代码不仅实现了需求,而且变得愈加简洁!重点是无需修改业务处理的代码,因此业务逻辑也繁杂,函数式风格的优势越明显。完整代码请参见:[附录III]。</p>
<h1>轮到你了!</h1>
<p>客户对这次重构非常满意!这一回,他们希望<code>foreach</code>能改成并行,即每个循环体都在独立的线程中执行,那效率又会得到飞跃。</p>
<p>现在轮到你了,你会如何实现客户的需求?</p>
<h1>附录I:面向对象风格代码</h1>
<div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="cp">#include <stdlib.h></span>
<span class="cp">#include <stdio.h></span>
<span class="k">typedef</span> <span class="kt">char</span> <span class="n">String</span><span class="p">[</span><span class="mi">32</span><span class="p">];</span>
<span class="k">typedef</span> <span class="kt">FILE</span><span class="o">*</span> <span class="n">File</span><span class="p">;</span>
<span class="k">typedef</span> <span class="k">struct</span> <span class="n">_Employee</span> <span class="p">{</span>
<span class="n">String</span> <span class="n">name</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">age</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">salary</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">_Employee</span> <span class="o">*</span><span class="n">next</span><span class="p">;</span>
<span class="p">}</span> <span class="o">*</span><span class="n">Employee</span><span class="p">;</span>
<span class="cm">/* Destructor */</span>
<span class="kt">void</span> <span class="nf">employee_free</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Employee</span> <span class="n">p</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span><span class="n">p</span> <span class="o">=</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="n">e</span> <span class="o">=</span> <span class="n">e</span><span class="o">-></span><span class="n">next</span><span class="p">;</span>
<span class="n">free</span><span class="p">(</span><span class="n">p</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="cm">/* Input */</span>
<span class="k">static</span> <span class="n">Employee</span> <span class="nf">employee_read_node</span><span class="p">(</span><span class="n">File</span> <span class="n">istream</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Employee</span> <span class="n">e</span> <span class="o">=</span> <span class="p">(</span><span class="n">Employee</span><span class="p">)</span><span class="n">calloc</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">_Employee</span><span class="p">));</span>
<span class="k">if</span> <span class="p">(</span><span class="n">e</span> <span class="o">!=</span> <span class="nb">NULL</span> <span class="o">&&</span> <span class="n">fscanf</span><span class="p">(</span><span class="n">istream</span><span class="p">,</span> <span class="s">"%s%d%d"</span><span class="p">,</span> <span class="n">e</span><span class="o">-></span><span class="n">name</span><span class="p">,</span> <span class="o">&</span><span class="n">e</span><span class="o">-></span><span class="n">age</span><span class="p">,</span> <span class="o">&</span><span class="n">e</span><span class="o">-></span><span class="n">salary</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">3</span><span class="p">)</span> <span class="p">{</span>
<span class="n">employee_free</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
<span class="n">e</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">e</span><span class="p">;</span>
<span class="p">}</span>
<span class="n">Employee</span> <span class="nf">employee_read</span><span class="p">(</span><span class="n">File</span> <span class="n">istream</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Employee</span> <span class="n">e</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">head</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">tail</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span><span class="n">e</span> <span class="o">=</span> <span class="n">employee_read_node</span><span class="p">(</span><span class="n">istream</span><span class="p">))</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">head</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
<span class="n">tail</span><span class="o">-></span><span class="n">next</span> <span class="o">=</span> <span class="n">e</span><span class="p">;</span>
<span class="n">tail</span> <span class="o">=</span> <span class="n">e</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="n">head</span> <span class="o">=</span> <span class="n">tail</span> <span class="o">=</span> <span class="n">e</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">head</span><span class="p">;</span>
<span class="p">}</span>
<span class="cm">/* Output */</span>
<span class="kt">void</span> <span class="nf">employee_print</span><span class="p">(</span><span class="n">File</span> <span class="n">ostream</span><span class="p">,</span> <span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(;</span> <span class="n">e</span><span class="p">;</span> <span class="n">e</span> <span class="o">=</span> <span class="n">e</span><span class="o">-></span><span class="n">next</span><span class="p">)</span> <span class="p">{</span>
<span class="n">fprintf</span><span class="p">(</span><span class="n">ostream</span><span class="p">,</span> <span class="s">"%s %d %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">e</span><span class="o">-></span><span class="n">name</span><span class="p">,</span> <span class="n">e</span><span class="o">-></span><span class="n">age</span><span class="p">,</span> <span class="n">e</span><span class="o">-></span><span class="n">salary</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="cm">/* Business Logic */</span>
<span class="kt">void</span> <span class="nf">employee_adjust_salary</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(;</span> <span class="n">e</span><span class="p">;</span> <span class="n">e</span> <span class="o">=</span> <span class="n">e</span><span class="o">-></span><span class="n">next</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">e</span><span class="o">-></span><span class="n">salary</span> <span class="o"><</span> <span class="mi">30000</span><span class="p">)</span> <span class="p">{</span>
<span class="n">e</span><span class="o">-></span><span class="n">salary</span> <span class="o">+=</span> <span class="mi">3000</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
<span class="n">File</span> <span class="n">istream</span><span class="p">,</span> <span class="n">ostream</span><span class="p">;</span>
<span class="n">Employee</span> <span class="n">e</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="n">istream</span> <span class="o">=</span> <span class="n">fopen</span><span class="p">(</span><span class="s">"work.txt"</span><span class="p">,</span> <span class="s">"r"</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">istream</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
<span class="n">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">"Cannot open work.txt with r mode.</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="n">exit</span><span class="p">(</span><span class="n">EXIT_FAILURE</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">e</span> <span class="o">=</span> <span class="n">employee_read</span><span class="p">(</span><span class="n">istream</span><span class="p">);</span>
<span class="n">fclose</span><span class="p">(</span><span class="n">istream</span><span class="p">);</span>
<span class="n">employee_print</span><span class="p">(</span><span class="n">stdout</span><span class="p">,</span> <span class="n">e</span><span class="p">);</span>
<span class="n">employee_adjust_salary</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
<span class="n">employee_print</span><span class="p">(</span><span class="n">stdout</span><span class="p">,</span> <span class="n">e</span><span class="p">);</span>
<span class="n">ostream</span> <span class="o">=</span> <span class="n">fopen</span><span class="p">(</span><span class="s">"work.txt"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">ostream</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
<span class="n">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">"Cannot open work.txt with w mode.</span><span class="se">\n</span><span class="s">"</span><span class="p">);</span>
<span class="n">exit</span><span class="p">(</span><span class="n">EXIT_FAILURE</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">employee_print</span><span class="p">(</span><span class="n">ostream</span><span class="p">,</span> <span class="n">e</span><span class="p">);</span>
<span class="n">fclose</span><span class="p">(</span><span class="n">ostream</span><span class="p">);</span>
<span class="n">employee_free</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
<span class="k">return</span> <span class="n">EXIT_SUCCESS</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<h1>附录II:函数式风格代码</h1>
<div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="cp">#include <stdlib.h></span>
<span class="cp">#include <stdio.h></span>
<span class="k">typedef</span> <span class="kt">char</span> <span class="n">String</span><span class="p">[</span><span class="mi">32</span><span class="p">];</span>
<span class="k">typedef</span> <span class="kt">FILE</span><span class="o">*</span> <span class="n">File</span><span class="p">;</span>
<span class="k">typedef</span> <span class="k">struct</span> <span class="n">_Employee</span> <span class="p">{</span>
<span class="n">String</span> <span class="n">name</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">age</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">salary</span><span class="p">;</span>
<span class="k">struct</span> <span class="n">_Employee</span> <span class="o">*</span><span class="n">next</span><span class="p">;</span>
<span class="p">}</span> <span class="o">*</span><span class="n">Employee</span><span class="p">;</span>
<span class="k">typedef</span> <span class="nf">void</span> <span class="p">(</span><span class="o">*</span><span class="n">Callback</span><span class="p">)(</span><span class="n">Employee</span><span class="p">);</span>
<span class="cm">/* High Order Functions */</span>
<span class="kt">void</span> <span class="nf">foreach</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">,</span> <span class="n">Callback</span> <span class="n">fn</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Employee</span> <span class="n">p</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span><span class="n">p</span> <span class="o">=</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="n">e</span> <span class="o">=</span> <span class="n">e</span><span class="o">-></span><span class="n">next</span><span class="p">;</span> <span class="cm">/* Avoid *next be changed in fn */</span>
<span class="n">fn</span><span class="p">(</span><span class="n">p</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="nf">with_open_file</span><span class="p">(</span><span class="n">String</span> <span class="n">filename</span><span class="p">,</span> <span class="n">String</span> <span class="n">mode</span><span class="p">,</span> <span class="n">Callback</span> <span class="n">fn</span><span class="p">,</span> <span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="n">File</span> <span class="n">file</span> <span class="o">=</span> <span class="n">freopen</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="n">mode</span><span class="p">,</span> <span class="p">(</span><span class="n">mode</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="sc">'r'</span><span class="o">?</span> <span class="n">stdin</span><span class="o">:</span> <span class="n">stdout</span><span class="p">));</span>
<span class="k">if</span> <span class="p">(</span><span class="n">file</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
<span class="n">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">"Cannot open %s with %s mode.</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">filename</span><span class="p">,</span> <span class="n">mode</span><span class="p">);</span>
<span class="n">exit</span><span class="p">(</span><span class="n">EXIT_FAILURE</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">fn</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
<span class="n">fclose</span><span class="p">(</span><span class="n">file</span><span class="p">);</span>
<span class="p">}</span>
<span class="cm">/* Destructor */</span>
<span class="k">static</span> <span class="kt">void</span> <span class="nf">employee_free_node</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">e</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
<span class="n">free</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="nf">employee_free</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="n">foreach</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="n">employee_free_node</span><span class="p">);</span>
<span class="p">}</span>
<span class="cm">/* Input */</span>
<span class="k">static</span> <span class="kt">void</span> <span class="nf">employee_read_node</span><span class="p">(</span><span class="n">Employee</span> <span class="n">node</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Employee</span> <span class="n">e</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">,</span> <span class="o">*</span><span class="n">head</span> <span class="o">=</span> <span class="p">(</span><span class="n">Employee</span><span class="o">*</span><span class="p">)</span> <span class="n">node</span><span class="p">;</span>
<span class="n">e</span> <span class="o">=</span> <span class="o">*</span><span class="n">head</span> <span class="o">=</span> <span class="p">(</span><span class="n">Employee</span><span class="p">)</span><span class="n">calloc</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">_Employee</span><span class="p">));</span>
<span class="k">if</span> <span class="p">(</span><span class="n">e</span> <span class="o">!=</span> <span class="nb">NULL</span> <span class="o">&&</span> <span class="n">scanf</span><span class="p">(</span><span class="s">"%s%d%d"</span><span class="p">,</span> <span class="n">e</span><span class="o">-></span><span class="n">name</span><span class="p">,</span> <span class="o">&</span><span class="n">e</span><span class="o">-></span><span class="n">age</span><span class="p">,</span> <span class="o">&</span><span class="n">e</span><span class="o">-></span><span class="n">salary</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">3</span><span class="p">)</span> <span class="p">{</span>
<span class="n">employee_free</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
<span class="o">*</span><span class="n">head</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="nf">employee_read</span><span class="p">(</span><span class="n">Employee</span> <span class="n">list</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Employee</span> <span class="n">e</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">,</span> <span class="o">*</span><span class="n">head</span> <span class="o">=</span> <span class="p">(</span><span class="n">Employee</span><span class="o">*</span><span class="p">)</span> <span class="n">list</span><span class="p">,</span> <span class="n">tail</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="o">*</span><span class="n">head</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="k">while</span> <span class="p">(</span><span class="n">employee_read_node</span><span class="p">((</span><span class="n">Employee</span><span class="p">)</span><span class="o">&</span><span class="n">e</span><span class="p">),</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="o">*</span><span class="n">head</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
<span class="n">tail</span><span class="o">-></span><span class="n">next</span> <span class="o">=</span> <span class="n">e</span><span class="p">;</span>
<span class="n">tail</span> <span class="o">=</span> <span class="n">e</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="o">*</span><span class="n">head</span> <span class="o">=</span> <span class="n">tail</span> <span class="o">=</span> <span class="n">e</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="cm">/* Output */</span>
<span class="k">static</span> <span class="kt">void</span> <span class="nf">employee_print_node</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"%s %d %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">e</span><span class="o">-></span><span class="n">name</span><span class="p">,</span> <span class="n">e</span><span class="o">-></span><span class="n">age</span><span class="p">,</span> <span class="n">e</span><span class="o">-></span><span class="n">salary</span><span class="p">);</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="nf">employee_print</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="n">foreach</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="n">employee_print_node</span><span class="p">);</span>
<span class="p">}</span>
<span class="cm">/* Business Logic */</span>
<span class="k">static</span> <span class="kt">void</span> <span class="nf">employee_adjust_salary_node</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">e</span><span class="o">-></span><span class="n">salary</span> <span class="o"><</span> <span class="mi">30000</span><span class="p">)</span> <span class="p">{</span>
<span class="n">e</span><span class="o">-></span><span class="n">salary</span> <span class="o">+=</span> <span class="mi">3000</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="nf">employee_adjust_salary</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="n">foreach</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="n">employee_adjust_salary_node</span><span class="p">);</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Employee</span> <span class="n">e</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="n">with_open_file</span><span class="p">(</span><span class="s">"work.txt"</span><span class="p">,</span> <span class="s">"r"</span><span class="p">,</span> <span class="n">employee_read</span><span class="p">,</span> <span class="p">(</span><span class="n">Employee</span><span class="p">)</span><span class="o">&</span><span class="n">e</span><span class="p">);</span>
<span class="n">employee_print</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
<span class="n">employee_adjust_salary</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
<span class="n">employee_print</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
<span class="n">with_open_file</span><span class="p">(</span><span class="s">"work.txt"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">,</span> <span class="n">employee_print</span><span class="p">,</span> <span class="n">e</span><span class="p">);</span>
<span class="n">employee_free</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
<span class="k">return</span> <span class="n">EXIT_SUCCESS</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<h1>附录III:改造成用数组的代码</h1>
<div class="highlight"><pre><code class="c language-c" data-lang="c"><span class="cp">#include <stdlib.h></span>
<span class="cp">#include <stdio.h></span>
<span class="k">typedef</span> <span class="kt">char</span> <span class="n">String</span><span class="p">[</span><span class="mi">32</span><span class="p">];</span>
<span class="k">typedef</span> <span class="kt">FILE</span><span class="o">*</span> <span class="n">File</span><span class="p">;</span>
<span class="k">typedef</span> <span class="k">struct</span> <span class="n">_Employee</span> <span class="p">{</span>
<span class="n">String</span> <span class="n">name</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">age</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">salary</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">length</span><span class="p">;</span>
<span class="p">}</span> <span class="o">*</span><span class="n">Employee</span><span class="p">;</span>
<span class="k">typedef</span> <span class="nf">void</span> <span class="p">(</span><span class="o">*</span><span class="n">Callback</span><span class="p">)(</span><span class="n">Employee</span><span class="p">);</span>
<span class="cm">/* High Order Functions */</span>
<span class="kt">void</span> <span class="nf">foreach</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">,</span> <span class="n">Callback</span> <span class="n">fn</span><span class="p">)</span> <span class="p">{</span>
<span class="kt">int</span> <span class="n">i</span><span class="p">,</span> <span class="n">length</span> <span class="o">=</span> <span class="n">e</span><span class="o">-></span><span class="n">length</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">length</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">fn</span><span class="p">(</span><span class="n">e</span><span class="o">++</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="nf">with_open_file</span><span class="p">(</span><span class="n">String</span> <span class="n">filename</span><span class="p">,</span> <span class="n">String</span> <span class="n">mode</span><span class="p">,</span> <span class="n">Callback</span> <span class="n">fn</span><span class="p">,</span> <span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="n">File</span> <span class="n">file</span> <span class="o">=</span> <span class="n">freopen</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="n">mode</span><span class="p">,</span> <span class="p">(</span><span class="n">mode</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">==</span> <span class="sc">'r'</span><span class="o">?</span> <span class="n">stdin</span><span class="o">:</span> <span class="n">stdout</span><span class="p">));</span>
<span class="k">if</span> <span class="p">(</span><span class="n">file</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
<span class="n">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">"Cannot open %s with %s mode.</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">filename</span><span class="p">,</span> <span class="n">mode</span><span class="p">);</span>
<span class="n">exit</span><span class="p">(</span><span class="n">EXIT_FAILURE</span><span class="p">);</span>
<span class="p">}</span>
<span class="n">fn</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
<span class="n">fclose</span><span class="p">(</span><span class="n">file</span><span class="p">);</span>
<span class="p">}</span>
<span class="cm">/* Destructor */</span>
<span class="kt">void</span> <span class="nf">employee_free</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="n">free</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
<span class="p">}</span>
<span class="cm">/* Input */</span>
<span class="kt">void</span> <span class="nf">employee_read_node</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="n">scanf</span><span class="p">(</span><span class="s">"%s%d%d"</span><span class="p">,</span> <span class="n">e</span><span class="o">-></span><span class="n">name</span><span class="p">,</span> <span class="o">&</span><span class="n">e</span><span class="o">-></span><span class="n">age</span><span class="p">,</span> <span class="o">&</span><span class="n">e</span><span class="o">-></span><span class="n">salary</span><span class="p">);</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="nf">employee_read</span><span class="p">(</span><span class="n">Employee</span> <span class="n">list</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Employee</span> <span class="n">e</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="kt">int</span> <span class="n">size</span><span class="p">;</span>
<span class="n">scanf</span><span class="p">(</span><span class="s">"%d"</span><span class="p">,</span> <span class="o">&</span><span class="n">size</span><span class="p">);</span>
<span class="o">*</span><span class="p">((</span><span class="n">Employee</span><span class="o">*</span><span class="p">)</span> <span class="n">list</span><span class="p">)</span> <span class="o">=</span> <span class="n">e</span> <span class="o">=</span> <span class="p">(</span><span class="n">Employee</span><span class="p">)</span><span class="n">calloc</span><span class="p">(</span><span class="n">size</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="k">struct</span> <span class="n">_Employee</span><span class="p">));</span>
<span class="n">e</span><span class="o">-></span><span class="n">length</span> <span class="o">=</span> <span class="n">size</span><span class="p">;</span>
<span class="n">foreach</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="n">employee_read_node</span><span class="p">);</span>
<span class="p">}</span>
<span class="cm">/* Output */</span>
<span class="kt">void</span> <span class="nf">employee_print_node</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"%s %d %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">e</span><span class="o">-></span><span class="n">name</span><span class="p">,</span> <span class="n">e</span><span class="o">-></span><span class="n">age</span><span class="p">,</span> <span class="n">e</span><span class="o">-></span><span class="n">salary</span><span class="p">);</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="nf">employee_print</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="n">printf</span><span class="p">(</span><span class="s">"%d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">e</span><span class="o">-></span><span class="n">length</span><span class="p">);</span>
<span class="n">foreach</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="n">employee_print_node</span><span class="p">);</span>
<span class="p">}</span>
<span class="cm">/* Business Logic */</span>
<span class="kt">void</span> <span class="nf">employee_adjust_salary_node</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">e</span><span class="o">-></span><span class="n">salary</span> <span class="o"><</span> <span class="mi">30000</span><span class="p">)</span> <span class="p">{</span>
<span class="n">e</span><span class="o">-></span><span class="n">salary</span> <span class="o">+=</span> <span class="mi">3000</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kt">void</span> <span class="nf">employee_adjust_salary</span><span class="p">(</span><span class="n">Employee</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
<span class="n">foreach</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="n">employee_adjust_salary_node</span><span class="p">);</span>
<span class="p">}</span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Employee</span> <span class="n">e</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
<span class="n">with_open_file</span><span class="p">(</span><span class="s">"work.array"</span><span class="p">,</span> <span class="s">"r"</span><span class="p">,</span> <span class="n">employee_read</span><span class="p">,</span> <span class="p">(</span><span class="n">Employee</span><span class="p">)</span><span class="o">&</span><span class="n">e</span><span class="p">);</span>
<span class="n">employee_print</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
<span class="n">employee_adjust_salary</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
<span class="n">employee_print</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
<span class="n">with_open_file</span><span class="p">(</span><span class="s">"work.array"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">,</span> <span class="n">employee_print</span><span class="p">,</span> <span class="n">e</span><span class="p">);</span>
<span class="n">employee_free</span><span class="p">(</span><span class="n">e</span><span class="p">);</span>
<span class="k">return</span> <span class="n">EXIT_SUCCESS</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>
<h1>附录IV:Common Lisp的解决方案</h1>
<p>从函数式风格重构的过程能体会到,如果C语言能支持动态类型,那就不必在<code>employee_read</code>中做强制转换;如果C语言支持匿名函数,亦不用写这么多小函数;如果C语言除了能读入整型、字符串等基础类型,还能只能读入数组、结构体等复合类型,就无需<code>employee_read</code>和<code>employee_print</code>等输入输出函数……</p>
<p>许多对函数式风格支持更好的编程语言(如Python、Ruby、Lisp等)已经让这些“如果”变成现实!看看Common Lisp的解决方案:</p>
<div class="highlight"><pre><code class="common-lisp language-common-lisp" data-lang="common-lisp"><span class="p">(</span><span class="nb">defparameter</span> <span class="nv">e</span> <span class="p">(</span><span class="nb">with-open-file</span> <span class="p">(</span><span class="nv">f</span> <span class="l-Other">#P"work.lisp"</span><span class="p">)</span> <span class="p">(</span><span class="nb">read</span> <span class="nv">f</span><span class="p">)))</span>
<span class="p">(</span><span class="nb">print</span> <span class="nv">e</span><span class="p">)</span>
<span class="p">(</span><span class="nb">dolist</span> <span class="p">(</span><span class="nv">p</span> <span class="nv">e</span><span class="p">)</span>
<span class="p">(</span><span class="k">if</span> <span class="p">(</span><span class="nb"><</span> <span class="p">(</span><span class="nb">third</span> <span class="nv">p</span><span class="p">)</span> <span class="mi">30000</span><span class="p">)</span>
<span class="p">(</span><span class="nb">incf</span> <span class="p">(</span><span class="nb">third</span> <span class="nv">p</span><span class="p">)</span> <span class="mi">3000</span><span class="p">)))</span>
<span class="p">(</span><span class="nb">print</span> <span class="nv">e</span><span class="p">)</span>
<span class="p">(</span><span class="nb">with-open-file</span> <span class="p">(</span><span class="nv">f</span> <span class="l-Other">#P"work.lisp"</span> <span class="ss">:direction</span> <span class="ss">:output</span><span class="p">)</span> <span class="p">(</span><span class="nb">print</span> <span class="nv">e</span> <span class="nv">f</span><span class="p">))</span>
</code></pre></div>
<p>尝试用你自己熟悉的编程语言解决这个问题,并评估它的可维护性。</p>
http://zzp.me/2013-04-28/replace-with-auto-increment-sequence/
替换成连续的数列
2013-04-28T10:13:54Z
2013-04-28T10:13:54Z
张泽鹏
http://zzp.me/
<h1>问题描述</h1>
<p>日常开发中有一类编辑操作,需要用一组连续的数列依次替换某个内容。比如要把下面的内容:</p>
<div class="highlight"><pre><code class="text language-text" data-lang="text">insert into tbl values ('JOE0');
insert into tbl values ('JOE0');
insert into tbl values ('JOE0');
insert into tbl values ('JOE0');
insert into tbl values ('JOE0');
</code></pre></div>
<p>替换成如下内容:</p>
<div class="highlight"><pre><code class="text language-text" data-lang="text">insert into tbl values ('JOE1');
insert into tbl values ('JOE2');
insert into tbl values ('JOE3');
insert into tbl values ('JOE4');
insert into tbl values ('JOE5');
</code></pre></div>
<p>即把0自动替换成一个递增的序列。下面用Emacs抛砖引玉。</p>
<h1>相关Emacs特性</h1>
<p>大部分编辑器都支持正则表达式替换,允许使用小括号分组并在后面的替换中使用诸如<code>\1</code>、<code>\2</code>、<code>\3</code>等向前引用。比如把<code><h1></code>和<code><h2></code>等标题的内容清空可以使用“<code>\(<h[12]>\)[^<]*</code>”替换成“<code>\1</code>”,其中<code>\(</code>和<code>\)</code>就是捕捉分组,<code>\1</code>则是引用捕捉到的内容。</p>
<p>Emacs中不仅能支持<code>\1</code>、<code>\2</code>等向前引用的方法,还提供了更高级的功能:“<code>\,</code>”。“<code>\,</code>”后面可以接一句Emacs Lisp表达式,它会把执行的结果替换到目标位置。比如把所有的HTML标签都替换成大写,即<code><backquote></code>替换<code><BACKQUOTE></code>,可以使用“<code>\(<[^>]+>\)</code>”替换成“<code>\,(upcase \1)</code>”,其中upcase是Emacs Lisp的函数,可以把一个字符串中的小写字母转成大写。</p>
<h1>解决方法</h1>
<p>使用上述特性解决文章开头的问题,只需使用incf函数即可,它的功能类似C语言中的 ++i。操作如下(其中“|”为光标所在位置):</p>
<ol>
<li><p>将光标移到起始位置</p>
<div class="highlight"><pre><code class="text language-text" data-lang="text">|insert into tbl values ('JOE0');
insert into tbl values ('JOE0');
insert into tbl values ('JOE0');
insert into tbl values ('JOE0');
insert into tbl values ('JOE0');
</code></pre></div></li>
<li><p>按C-M-%,即执行正则表达式替换</p></li>
<li><p>根据提示输入要替换的内容“0”,敲回车</p></li>
<li><p>再输入目标内容“\,(incf count)”,其中count为变量名</p></li>
<li><p>敲回车,得到如下内容。</p>
<div class="highlight"><pre><code class="text language-text" data-lang="text">insert into tbl values ('JOE1');
insert into tbl values ('JOE2');
insert into tbl values ('JOE3');
insert into tbl values ('JOE4');
insert into tbl values ('JOE5|');
</code></pre></div></li>
</ol>
http://zzp.me/2013-03-13/iknowledge-v1-release/
iKnowledge诞生记:知识vs经验
2013-03-13T21:31:46Z
2013-03-13T21:31:46Z
张泽鹏
http://zzp.me/
<h1>免费博客空间的尴尬史</h1>
<p>我其实开过好几个博客:</p>
<ol>
<li>第一个是网易163 博客,在高中时赶时髦开的。那时候忙着高考,加上高中的生活很单调(没有一丝爆点),所以成了摆设。毕业后尝试更新过几篇,但首页对文章的预览功能总让我不满意(好吧,我承认我有强迫症,看见那长短不一的预览就纠结),把心一狠就关掉了。</li>
<li>后来一位高中同学帮我开通了QQ 空间(那时QQ 空间还不能自己申请,需要其他已开通的用户帮忙开通),我想QQ 上人气也旺,可以遥相呼应、一石激起千层浪!可惜QQ 空间对文章大小作了很严格的限制,容不下我那么多废话,只能痛并快乐着。后来发生意外,腾讯突然说我的QQ 号是靓号,要求续费才能继续使用。苦于连换了三张手机卡都不成功,后来发现那些手机号也被移动和联通收回了,那可是我花钱买的号码呀!受不了这些霸王条约,再次狠心把帐号仍掉。</li>
<li>第三回转到<a href="http://hi.baidu.com/redraiment">百度空间</a>,上大一时开的。那时我觉得百度的产品都不会很激进地更新,这比较符合我的口味。尤其是能直接粘贴带HTML 格式的文本,写ACM 等解题报告感觉很方便。结果百度空间改版后对文章的大小也作了限制,贴的代码格式也经常被打乱格式,于是再次考虑搬家,可找半天也没找到能关闭空间的按钮,感觉很无奈...</li>
<li>第四回搬家到<a href="http://blog.csdn.net/redraiment">CSDN</a>,那是在08年的时候。我想在国内CSDN 算得上专业的程序员站点了,博客系统自然也应该有“程序员特色”吧。可它偏偏很不稳定,我刚夸完它就发现文章不能发表了,看了官方博客才知道“评论系统”也已停用半个多月了。这让我感觉不是很好,觉得他们有点不负责,拿用户当小白鼠。当然,CSDN 的新功能倒是不断添加,也自带插入代码的功能,用起来很方便。而且在那里也有很多志同道合的朋友会关注我的空间,让我很有存在感。只可惜Bug 修复效率不高。</li>
<li>第五回我选择了<a href="http://redraiment.blogspot.com/">Google Blogger</a>,在09年的时候。那段时间我是Google 产品的重度用户:浏览器主页是iGoogle、用Gmail 收发信件、在Reader 上吸收大师们的观点、用Docs 在线编写和共享我的文档、用Notebook 随时记录稍瞬即逝的灵感。那时候他们表现得如此稳定,比如用Gmail 发送大附件时没遇到苦苦等待十几分钟后却返回一个发送超时的尴尬,把事情交给它们处理我觉得很安心。“漂泊”这么久,我本希望Blogger 能让我安心地住下来,遗憾的是不到三天Blogger 就被和谐了。</li>
</ol>
<p>古人有孟母三迁,我的空间都搬五回家了,始终没找到称心如意的博客空间。Blogger被墙后我不得不回到CSDN,结果还混上了社区专家。</p>
<h1>知识vs经验</h1>
<p>从第一个163博客到现在,已经快有8个年头了。对于我而言,博客也从最初的吐槽工具变成了知识库。</p>
<p>说到知识(Knowledge),很多人都会把它与经验(experience)划等号,在我看来它们是不一样的:知识是抽象的,而经验是具体的。人们先在实践中获得经验,然后结合以前获得的经验在脑海中进一步分析和挖掘,融入自己的知识体系中。形成知识后就可以举一反三,去预测其他同类情况的结果。</p>
<p>比如幼儿园的小朋友学加法,老师举了1+1=2、2+2=4等一系列具体的例子,如果小朋友们紧紧在脑海中强记住这些等式,那他们知识掌握了几条独立的经验,这些经验就像一张Hash表一样,告诉小朋友1+1的结果是2、2+2的结果是4,一旦遇到新的加法问题(如1+2)就无从下手了;在将来小朋友们接触到了更多的数字和等式后,脑海中分析和挖掘这些等式的异同,最后提炼出整数和加法等抽象的概念,形成了知识,就能解决所有加法问题。</p>
<p>因此,我认为经验是通用的,经验只是对某个现象的描述,比如被针扎到会痛、1+1等于2;而知识是属于个人的,由于每个人不同的世界观,以及脑海中积累不同数量的经验,导致同一条经验不同的人会有不同的归类方式。比如提到Bash,有些人会把它与Linux划等号,因为他们只知道Linux下需要用bash;而另一些人知道它是一种Shell,因为他们知道还有csh、tcsh、zsh等其他shell,并且它们还能用于Linux意外的系统中。</p>
<h1>iKnowledge诞生</h1>
<p>每当我们学到新东西就能写一篇博文来记录,但这些记录还仅仅是一条条独立的经验,需要按照某种方式去组织它们,这样才能融入我们的知识体系。</p>
<p>纵观现有的博客系统,对于博文的组织无非是时间的倒序,或给文章贴标签。我发现很多人压根不会回头去看自己写的博客,即使博文排版混乱、文章主题不鲜明、错别字无数也都无所谓。</p>
<p>对我而言,博客不仅用于记录我的知识,方便以后查阅;更重要的是它要帮助我梳理已掌握的经验以形成知识体系,反而时间的先后并不重要。因此,我需要一套树状的结构来展示主题与主题之间的层次关系,而不是一堆摊平的标签。</p>
<p>由于上面种种原因,经过几天自学ExtJS,我终于开发出自己的博客系统,实现以层次结构组织的标签系统,并取名叫iKnowledge,以强调这套系统帮助大家积累知识,而不仅仅是记录经验。</p>
<p><img src="/resource/2013-03-13/iknowledge-v1-release/1.png" /></p>
<h1>iKnowledge的特色</h1>
<h2>iKnowledge是一个纯静态网站</h2>
<p>自从Paul Graham 开发了全世界第一个Web 应用程序Viaweb 之后,应用程序Web化变得一发不可收拾,甚至出现了Web OS 要完全取代桌面应用。主流博客系统中,既有前台展示还有后台管理,能在线发文章、编辑、删除等,什么都做结果什么都没做好。比如上面提的展示形式一成不变、在线编辑器功能单一、误删误改恢复困难等。正因如此,iKnowledge只做一件事——展示。文章编辑、版本控制等工作则交给专业的编辑器和版本控制工具来完成!</p>
<p>现有的博客系统几乎清一色使用数据库保存文章内容,通常还只使用一个字段。且不说性能如何,就使用上也是非常的不方便。诸如批量统计文章行数的功能,不得不写一个程序来解析字段里的字符串;而处理纯文本文件却有一堆现成的命令行工具。再说博客系统的数据通常是一次写入、偶尔编辑、多次读取,即频繁地查询。为了提高性能,又往往对动态页面做缓存处理生成静态页面。既然到头来都是生成静态页面,前面那些琐碎的事情简直是脱裤子放屁多此一举。</p>
<p>根据上面的需求描述,说白了iKnowledge就是左侧树状菜单、右侧选项卡页面的静态页面。为了快速实现这套系统,我选择站在巨人的肩膀上,很多功能使用现成的工具或库。</p>
<h2>iKnowledge基于ExtJS</h2>
<p>的确,它很臃肿。所以,非常感谢你耐心地浏览这个被全国99&#37电脑击败的龟速博客。</p>
<p>JavaScript被称为Web时代的汇编语言,虽然它相较其他高级语言已经灵活很多!Web程序员在经历这么多年的积累和沉淀后渐渐挖掘出一些Web编程的模式,于是有了jQuery等JS库来简化开发,这一飞跃犹如把汇编码中成堆的JNZ、JMP等语句组织成了if、for等C语言控制语句。但这还不够,开发者还得自己定义HTML、设计CSS样式等。于是ExtJS来了,犹如当年Delphi带来RAD的春风一般。它带来的不仅仅是漂亮的界面,还有开发过程的改进:程序员不再需要关心如何定义HTML、怎么写跨浏览器的CSS,只需确定数据展示的形式再调用相应的组件即可。</p>
<p>我有幸从JavaScript一直实践到ExtJS,所以我能体会每一次改进带来的喜悦!回到iKnowledge,正如上面说的,它只是一颗树加一个选项卡。如果从零开始实现,工作量无法想象;即使使用插件丰富的jQuery工作量也不小,何况jQuery的插件往往各自为政,相互协作总会出这样或那样的问题;而使用ExtJS,一切都这么简单!</p>
<p>前端相关的代码都在app这个目录中,整个工程只有32k。</p>
<h2>文件目录结构即标签层次结构</h2>
<p>提到树状结构,最常见的莫过于系统的文件目录结构,为什么我们不好好利用呢?iKnowledge就是利用文件目录结构生成左侧分类标签层次结构,转换工作由Rhino(纯Java实现的JavaScript引擎)自动完成。由此可见,iKnowledge是重度依赖JavaScript,无论前端还是后端,无不享受着它的强大与便利。</p>
<p>为了方便在命令行下切换目录,目录名统一用英文,但前端展示我又希望用中文,因此在生成的过程中做了额外的翻译工作。</p>
<h2>代码高亮</h2>
<p>作为程序员的博客,少不了在博文中分享代码,语法高亮的代码有助于读者理解代码逻辑。iKnowledge选用了一个原生的JavaScript插件——<a href="http://rainbowco.de/">Rainbow</a>,所谓原生的指不依赖注入jQuery等JavaScript库。</p>
<p>这个库很轻量(1.4kb),而且扩展也很方便,但在使用中发现它在处理较长的代码段时偶尔会堆栈溢出,作者表示无法重现问题。</p>
<h2>Q: 为什么不能评论?</h2>
<p>在这个Web 2.x时代,一个只能看的网站简直一无是处!毕竟提供评论等互动的渠道可以和读者交流,相互学习等。但愿望总是好的,以我CSDN上的博客为例,近70万的访问量咋看很厉害的样子,但总共只有680条评论,除去大部分单纯叫好、求帮的,真正有价值的评论没几条!</p>
<p>此外,要处理评论对服务器也有更多的要求;而放弃评论就只需一台能处理静态页面的Web服务器。这样我就能把iKnowledge托管在Github Pages上。</p>
http://zzp.me/2013-01-26/strip-if-with-chain-of-responsibility/
消灭分支语句之责任链模式
2013-01-26T22:47:00+08:00
2013-01-26T22:47:00+08:00
张泽鹏
http://zzp.me/
<p>分支语句是所有编程语言的基本元素,比如Java语言中的if else和switch语句,它们提供一种能力允许程序根据一些条件动态地选择执行某些代码块。这种动态性给程序带来了很多的灵活性!</p>
<p>正因为if else如此方便如此灵活,很多代码中它都会被滥用,就像下面这样让人崩溃的、嵌套的、成堆的分支语句:</p>
<div class="highlight"><pre><code class="java"><span class="k">if</span> <span class="o">(</span><span class="n">context</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="s">"tutorial-room"</span><span class="o">))</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">pageNumber</span> <span class="o">==</span> <span class="mi">1</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">input</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="s">"2"</span><span class="o">))</span> <span class="o">{</span>
<span class="c1">// go next</span>
<span class="c1">// output step-2 prompt</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="c1">// warning xxx</span>
<span class="c1">// output step-1 prompt</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">pageNumber</span> <span class="o">==</span> <span class="mi">2</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">state</span> <span class="o">==</span> <span class="n">State</span><span class="o">.</span><span class="na">QUITING</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">input</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="s">"y"</span><span class="o">))</span> <span class="o">{</span>
<span class="c1">// xxx</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="c1">// restore</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">input</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="s">"22"</span><span class="o">))</span> <span class="o">{</span>
<span class="c1">// put chess</span>
<span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">input</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="s">"q"</span><span class="o">))</span> <span class="o">{</span>
<span class="c1">// quiting?</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="c1">// unknow instruct</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">...</span>
<span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">context</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="s">"newbie-room"</span><span class="o">))</span> <span class="o">{</span>
<span class="o">...</span>
<span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">context</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="s">"easy-room"</span><span class="o">))</span> <span class="o">{</span>
<span class="o">...</span>
<span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">context</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="s">"normal-room"</span><span class="o">))</span> <span class="o">{</span>
<span class="o">...</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">...</span>
</code></pre></div>
<p>本文会讨论一些程序设计的方法,把诸如上述的混乱代码重构成更清晰更优雅的代码。注:文中的代码皆为Java代码片段,仅使用标准JDK的类库。</p>
<h1>问题</h1>
<p>说上述代码结构让人崩溃,我们得有理有据。</p>
<p>首先,它的可读性不好。这里说的可读性不好并非指变量名命名不规范、花括号风格不一致、对齐不统一等问题,而是指代码是否方便理解。比如:</p>
<div class="highlight"><pre><code class="java"><span class="k">if</span> <span class="o">(</span><span class="n">cash</span> <span class="o"><</span> <span class="n">price</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// block A</span>
<span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">onSale</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// block B</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">...</span> <span class="n">block</span> <span class="n">C</span>
</code></pre></div>
<p>这段代码先检查用户的现金是否足够支付当前货物的价格,如果余额不足则执行代码块A,否则再查看当前货物是否有促销活动,有就执行代码块B。其中代码块B咋眼看只有if (onSale)这一个条件,但因为它处于else块中,所以还隐含了(cash >= price)这一条件。在代码规模不是很大的时候,这样的隐含条件影响可能不大,但如果有很多个else条件并且里面同时还嵌套着很深的分支结构,当你看到最深层的代码时,你是否还确信自己能清楚地记得所有的前提条件?</p>
<p>其次,它的维护性不好。比如在上面代码中加入会员机制,会员在购买商品时有积分,那相应的积分模块调用代码要同时出现在block B和block C中。如果之后会员又分了多个等级,那这段代码很快就成了庞然大物,任何的修改都会牵一发而动全身!</p>
<h1>查表法</h1>
<p>根据分支语句的特点,它可用于根据不同的输入返回特定的输出。比如《如此理解面向对象》一文中要根据系统名字,输出不同的提示语:</p>
<div class="highlight"><pre><code class="java"><span class="n">String</span> <span class="n">osName</span> <span class="o">=</span> <span class="n">System</span><span class="o">.</span><span class="na">getProperty</span><span class="o">(</span><span class="s">"os.name"</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">osName</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="s">"SunOS"</span><span class="o">))</span> <span class="o">{</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"This is a UNIX box and therefore good."</span><span class="o">);</span>
<span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">osName</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="s">"Linux"</span><span class="o">))</span> <span class="o">{</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"This is a Linux box and good as well."</span><span class="o">);</span>
<span class="o">}</span> <span class="k">else</span> <span class="k">if</span> <span class="o">(</span><span class="n">osName</span><span class="o">.</span><span class="na">equals</span><span class="o">(</span><span class="s">"Windows NT"</span><span class="o">))</span> <span class="o">{</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"This is a Windows box and therefore bad."</span><span class="o">);</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"Unknow box."</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div>
<p>我们暂且成这类分支为“数据型分支”。它犹如数学中的映射(Mapping),每一组特定的输入数据对应一组唯一的输出数据。因此,在输入数据比较简单时(比如第一个例子,输入数据只有系统名字一项),可以使用 java.util.Map 或 java.util.Properties 把映射关系持久化到配置文件中,程序启动时再加载到内存:</p>
<div class="highlight"><pre><code class="java"><span class="kn">import</span> <span class="nn">java.io.FileInputStream</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.Properties</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Main</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="n">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="n">Exception</span> <span class="o">{</span>
<span class="n">Properties</span> <span class="n">options</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Properties</span><span class="o">();</span>
<span class="n">options</span><span class="o">.</span><span class="na">load</span><span class="o">(</span><span class="k">new</span> <span class="n">FileInputStream</span><span class="o">(</span><span class="s">"options.properties"</span><span class="o">));</span>
<span class="n">String</span> <span class="n">osName</span> <span class="o">=</span> <span class="n">System</span><span class="o">.</span><span class="na">getProperty</span><span class="o">(</span><span class="s">"os.name"</span><span class="o">);</span>
<span class="n">String</span> <span class="n">prompt</span> <span class="o">=</span> <span class="n">options</span><span class="o">.</span><span class="na">getProperty</span><span class="o">(</span><span class="n">osName</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">prompt</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">prompt</span> <span class="o">=</span> <span class="s">"Unknow box."</span><span class="o">;</span>
<span class="o">}</span>
<span class="n">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="n">prompt</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div>
<p>其中配置文件信息如下:</p>
<div class="highlight"><pre><code class="ini"><span class="na">SunOS</span><span class="o">=</span><span class="s">This is a UNIX box and therefore good.</span>
<span class="na">Linux</span><span class="o">=</span><span class="s">This is a Linux box and good as well.</span>
<span class="na">Windows\ NT</span><span class="o">=</span><span class="s">This is a Windows box and therefore bad.</span>
</code></pre></div>
<p>使用这种方法,能很方便地支持新的系统或修改现有系统的提示语,且无须修改程序。不过开发中真实的输入项远不止一个字符串,正如 <a href="http://my.oschina.net/feichexia">@jxqlove?</a> 同学之前在 (<a href="http://www.oschina.net/code/snippet_111708_17599">http://www.oschina.net/code/snippet_111708_17599</a>) 中提的:根据交易类型、支付方式等多个条件返回一个字符串。处理这种Key有多个元素构成的情况,解决方案的思想和单元素是一致的,只是把元数据移到了数据库中:</p>
<div class="highlight"><pre><code class="sql"><span class="k">create</span> <span class="k">table</span> <span class="n">metadata</span> <span class="p">(</span>
<span class="n">trade_type</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">16</span><span class="p">),</span> <span class="c1">-- 交易类型,比如收入、支出等</span>
<span class="n">payment</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">16</span><span class="p">),</span> <span class="c1">-- 支付类型,比如现金、信用卡等</span>
<span class="n">code</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span> <span class="c1">-- 最终的返回值</span>
<span class="p">);</span>
<span class="k">insert</span> <span class="k">into</span> <span class="n">metadata</span> <span class="k">values</span> <span class="p">(</span><span class="s1">'income'</span><span class="p">,</span> <span class="s1">'cash'</span><span class="p">,</span> <span class="s1">'001'</span><span class="p">);</span>
<span class="k">insert</span> <span class="k">into</span> <span class="n">metadata</span> <span class="k">values</span> <span class="p">(</span><span class="s1">'income'</span><span class="p">,</span> <span class="s1">'credit card'</span><span class="p">,</span> <span class="s1">'002'</span><span class="p">);</span>
<span class="k">insert</span> <span class="k">into</span> <span class="n">metadata</span> <span class="k">values</span> <span class="p">(</span><span class="s1">'income'</span><span class="p">,</span> <span class="s1">'alipay'</span><span class="p">,</span> <span class="s1">'003'</span><span class="p">);</span>
<span class="k">insert</span> <span class="k">into</span> <span class="n">metadata</span> <span class="k">values</span> <span class="p">(</span><span class="s1">'expense'</span><span class="p">,</span> <span class="s1">'cash'</span><span class="p">,</span> <span class="s1">'101'</span><span class="p">);</span>
<span class="k">insert</span> <span class="k">into</span> <span class="n">metadata</span> <span class="k">values</span> <span class="p">(</span><span class="s1">'expense'</span><span class="p">,</span> <span class="s1">'credit card'</span><span class="p">,</span> <span class="s1">'102'</span><span class="p">);</span>
<span class="k">insert</span> <span class="k">into</span> <span class="n">metadata</span> <span class="k">values</span> <span class="p">(</span><span class="s1">'expense'</span><span class="p">,</span> <span class="s1">'alipay'</span><span class="p">,</span> <span class="s1">'103'</span><span class="p">);</span>
</code></pre></div>
<p>在应用程序这一端则需要动态地构造查询语句:</p>
<div class="highlight"><pre><code class="java"><span class="kd">public</span> <span class="n">String</span> <span class="nf">queryStatement</span><span class="o">(</span><span class="n">Properties</span> <span class="n">options</span><span class="o">)</span> <span class="o">{</span>
<span class="n">StringBuilder</span> <span class="n">query</span> <span class="o">=</span> <span class="k">new</span> <span class="n">StringBuilder</span><span class="o">(</span><span class="s">"select code from metadata"</span><span class="o">);</span>
<span class="n">Enumeration</span> <span class="n">names</span> <span class="o">=</span> <span class="n">options</span><span class="o">.</span><span class="na">propertyNames</span><span class="o">();</span>
<span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">names</span><span class="o">.</span><span class="na">hasMoreElements</span><span class="o">();</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span>
<span class="n">String</span> <span class="n">key</span> <span class="o">=</span> <span class="n">names</span><span class="o">.</span><span class="na">nextElement</span><span class="o">().</span><span class="na">toString</span><span class="o">();</span>
<span class="n">String</span> <span class="n">value</span> <span class="o">=</span> <span class="n">options</span><span class="o">.</span><span class="na">getProperty</span><span class="o">(</span><span class="n">key</span><span class="o">);</span>
<span class="n">query</span><span class="o">.</span><span class="na">append</span><span class="o">(</span><span class="n">i</span> <span class="o">==</span> <span class="mi">0</span><span class="o">?</span> <span class="s">" where "</span><span class="o">:</span> <span class="s">" and "</span><span class="o">);</span>
<span class="n">query</span><span class="o">.</span><span class="na">append</span><span class="o">(</span><span class="n">String</span><span class="o">.</span><span class="na">format</span><span class="o">(</span><span class="s">"%s = '%s'"</span><span class="o">,</span> <span class="n">key</span><span class="o">,</span> <span class="n">value</span><span class="o">));</span>
<span class="o">}</span>
<span class="k">return</span> <span class="n">query</span><span class="o">.</span><span class="na">toString</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div>
<p>根据实际的情况,代码可能更复杂一些,比如value的内容需要转义等。这样设计的系统会非常灵活,比如输入端新增了一个选项,只需给metadata添加新的字段,并根据所有的合法值插入新的记录或更新现有记录,而代码无须修改。</p>
<p>这种持久化到数据库的方法适用于一对一的无规律映射,即不存在或者只有少量的映射存在多组key对应同一个value的情况。它和数据的规模无关,比如一个字典程序的数据同样适用这种方式,数据量虽然很大但并不稀疏。</p>
<p>与之相对的是稀疏的数据,比如有一项值域范围是[1,100],其中1到50应对的值是无规律,从51到100的值全部是一个固定的常量(比如0)。这时候有一半的存储空间是浪费的,真心不如在代码里用 if (value > 50) 来判断。下文会提供另一种方法处理这类问题。</p>
<h1>类责任链模式</h1>
<p>上面介绍的查表法把元数据从逻辑代码中剥离出来,避免因元数据(Metadata)变化导致修改程序。但从某种意义上来说,程序本该如此:程序本身只是逻辑的集合;元数据(辅助程序行为,诸如语言包文件)集中在配置文件里;待处理的数据来自外部输入(用户手工录入、本地文件、数据库等)。因此本节讨论分支语句更常用的方式:选择执行某段代码。</p>
<div class="highlight"><pre><code class="java"><span class="k">if</span> <span class="o">(</span><span class="n">optionA</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">optionB</span><span class="o">)</span> <span class="o">{</span>
<span class="n">doSomething1</span><span class="o">();</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">doSomething2</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="n">doSomething3</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div>
<p>类似上面的代码,根据不同的输入选项或命令行参数等调用不同的方法来完成某些操作,而不是单纯的返回数据。因此,这些选项是为了确定现在这个request是谁的职责,而这正是“责任链模式”要解决的问题!本节的标题为“类责任链模式”,表示我的解决方案是类似“责任链模式”,并不严格和它保持一致,但核心思想是一致的:使多个对象都有机会处理请求。</p>
<p>因此,每个RequestHandler都需提供一个接口判断自己能否处理当前请求;如果能处理,则Client调用另一个执行的接口:</p>
<div class="highlight"><pre><code class="java"><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">Handler</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">accept</span><span class="o">(</span><span class="n">Properties</span> <span class="n">options</span><span class="o">);</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">execute</span><span class="o">();</span>
<span class="o">}</span>
</code></pre></div>
<p>于是,上面的分支结构对应三个独立的Handler类:</p>
<div class="highlight"><pre><code class="java"><span class="kd">public</span> <span class="kd">class</span> <span class="nc">RequestHandler1</span> <span class="kd">implements</span> <span class="n">Handler</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">accept</span><span class="o">(</span><span class="n">Properties</span> <span class="n">options</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">options</span><span class="o">.</span><span class="na">getProperty</span><span class="o">(</span><span class="s">"A"</span><span class="o">)</span> <span class="o">!=</span> <span class="kc">null</span>
<span class="o">&</span><span class="n">amp</span><span class="o">;&</span><span class="n">amp</span><span class="o">;</span> <span class="n">options</span><span class="o">.</span><span class="na">getProperty</span><span class="o">(</span><span class="s">"B"</span><span class="o">)</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">execute</span><span class="o">()</span> <span class="o">{</span>
<span class="n">doSomething1</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">RequestHandler2</span> <span class="kd">implements</span> <span class="n">Handler</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">accept</span><span class="o">(</span><span class="n">Properties</span> <span class="n">options</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">options</span><span class="o">.</span><span class="na">getProperty</span><span class="o">(</span><span class="s">"A"</span><span class="o">)</span> <span class="o">!=</span> <span class="kc">null</span>
<span class="o">&</span><span class="n">amp</span><span class="o">;&</span><span class="n">amp</span><span class="o">;</span> <span class="n">options</span><span class="o">.</span><span class="na">getProperty</span><span class="o">(</span><span class="s">"B"</span><span class="o">)</span> <span class="o">==</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">execute</span><span class="o">()</span> <span class="o">{</span>
<span class="n">doSomething2</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">RequestHandler3</span> <span class="kd">implements</span> <span class="n">Handler</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">accept</span><span class="o">(</span><span class="n">Properties</span> <span class="n">options</span><span class="o">)</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">options</span><span class="o">.</span><span class="na">getProperty</span><span class="o">(</span><span class="s">"A"</span><span class="o">)</span> <span class="o">==</span> <span class="kc">null</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">execute</span><span class="o">()</span> <span class="o">{</span>
<span class="n">doSomething3</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div>
<p>接下来还需要一个额外的管理类负责这些类的实例化的请求的分发:</p>
<div class="highlight"><pre><code class="java"><span class="kn">import</span> <span class="nn">java.util.ServiceLoader</span><span class="o">;</span>
<span class="kn">import</span> <span class="nn">java.util.Iterator</span><span class="o">;</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Manager</span> <span class="o">{</span>
<span class="kd">private</span> <span class="kd">static</span> <span class="n">Arraylist</span><span class="o">;</span>
<span class="kd">static</span> <span class="o">{</span>
<span class="n">list</span> <span class="o">=</span> <span class="k">new</span> <span class="n">Array</span><span class="o">();</span>
<span class="n">ServiceLoaderloader</span> <span class="o">=</span> <span class="n">ServiceLoader</span><span class="o">.</span><span class="na">load</span><span class="o">(</span><span class="n">Handler</span><span class="o">.</span><span class="na">class</span><span class="o">);</span>
<span class="n">Iteratorit</span> <span class="o">=</span> <span class="n">loader</span><span class="o">.</span><span class="na">iterator</span><span class="o">();</span>
<span class="k">while</span> <span class="o">(</span><span class="n">it</span><span class="o">.</span><span class="na">hasNext</span><span class="o">())</span> <span class="o">{</span>
<span class="n">list</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">it</span><span class="o">.</span><span class="na">next</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">process</span><span class="o">(</span><span class="n">Properties</span> <span class="n">options</span><span class="o">)</span> <span class="o">{</span>
<span class="k">for</span> <span class="o">(</span><span class="n">Handler</span> <span class="n">handler</span> <span class="o">:</span> <span class="n">list</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">handler</span><span class="o">.</span><span class="na">accept</span><span class="o">(</span><span class="n">options</span><span class="o">))</span> <span class="o">{</span>
<span class="n">handler</span><span class="o">.</span><span class="na">execute</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div>
<p>上面代码使用了服务加载功能自动实例化所有注册过的Handler子类,如果你还不了解它的原理,可查看相应的API文档。有了这些代码,已经万事具备!也许你已经发现,这样的设计和JDBC的接口不谋而合:Manager对应java.sql.DriverManager、Handler对应java.sql.Driver、RequestHandler这些类则对应数据库厂商自己实现的驱动程序。</p>
<p>基于这样的框架,它的代码总量也许比原来的要多,但你不再需要在一堆if else中仔细推敲代码执行的前提条件,所有的前提条件都在accept函数里;添加新的功能所要做的仅需实现一个新的类,无须修改现有代码,符合开闭原则。</p>
<h1>总结</h1>
<p>本文中介绍了两种方法在我的实际开发中运用很多。比如昨天分享的“微信版开窗游戏”就是用“类责任链模式”结合“状态模式”实现的(不过它不是用Java写的)。如果你有其他方法来处理上述问题,欢迎留言交流。感想你耐心地读完全文!</p>
<p>PS:其实消灭分支语句的方法还有很多,也许可以继续写一个系列~嘿嘿。</p>
http://zzp.me/2012-10-21/sparrow-known-noble-ones/
燕雀安知鸿鹄之志
2012-10-21T23:28:00+08:00
2012-10-21T23:28:00+08:00
张泽鹏
http://zzp.me/
<h1>太阳能手电筒</h1>
<p>昨天参加完OSC源创会之后,和几个同事一起在肯德基啃鸡腿。期间聊起我前天开发的“中文计算器”,同事的想法和网上的评论差不多,觉得这是一个无聊的作品,没什么现实意义。顺着这个话题,又聊到什么是最没用的发明?</p>
<p>同事说当初看《国产凌凌漆》的时候,达文西发现的那个“太阳能手电筒”是他看来最没用的!手电筒本来就是在黑暗的地方用来照明的,而他的发明只能在有光的地方才亮,在黑暗的地方就“绝对不会亮”。</p>
<p><img src="/resource/2012-10-21/sparrow-known-noble-ones/1.png" /></p>
<p>但他的观点遭到我们的反对。所谓“垃圾是放错地方的宝贝”,换个角度来看,其实这是一个很伟大的发明!</p>
<p>普通的手电筒一打开开关就一直工作下去直到把电量耗尽,这好比执行一段 while(true) 的死循环代码,直到机器资源耗尽才停止;但达文西的发明是“事件触发式”的,虽然启动了,但大多数时候处于 sleep 状态,不消耗 CPU 等资源,直到收到一个信号(有了其他光源的照射),它才开始工作。</p>
<p>用它的光来照明当然显得多余,当如果场景变成公路旁的广告牌,在漆黑的夜晚没人经过时它也不亮;当有一辆车开过时,汽车的车灯就作为信号点,广告牌就能被点亮。采用这种方式,就只在有人经过的时候才点亮,能省下不少电!你能说这不是一项伟大的发明?</p>
<h1>施乐和乔布斯</h1>
<p>人们对于新事物总是保守的,就像法拉第刚发明发电机时、贝尔刚发明电话时,人们都不知道这些东西有什么用,觉得是没用的东西。离我们近的还有施乐(Xerox)的研究员们发明图形界面和鼠标时,那些董事长也都不以为然,偏偏乔布斯独具慧眼,认准这是革命性的发明!想了解这段历史的童鞋可以去看《硅谷传奇》这部电影。反思我们自己,之前是不是也随大流否定、嘲笑了很多新事物,结果失去了很多机会。</p>
<p>最后,用《国产凌凌漆》中的一句台词结尾:就算是一张卫生纸、一条内裤都有它本身的用处!</p>
<p><img src="/resource/2012-10-21/sparrow-known-noble-ones/2.png" /></p>
http://zzp.me/2012-09-25/node-js-introduct/
Node.js 初体验
2012-09-25T23:10:00+08:00
2012-09-25T23:10:00+08:00
张泽鹏
http://zzp.me/
<p>又到周五晚上自由时间,^_^。今天看了一下 Node.js。</p>
<h1>服务器端 JS 情缘</h1>
<p>在校期间我学会了JavaScript和Java,当时我就在考虑JS有没有类似JSP一样的服务器端程序,名字应该是JSSP(JavaScript Server Page),可以在 HTML 中嵌入 JS。Google了一圈发现IIS支持用JScript代替VBScript做ASP开发,另外SourceForge上真有个叫JSSP的项目,以及今天的主角Node.js。当时的Node.js刚起步,首页背景还是黑乎乎的(不晓得其他童鞋是否也有印象)。经过一圈比较,我最终选择使用Rhino——一个纯Java实现的JS引擎,它吸引我的地方是能直接调用Java类库。</p>
<h1>Node.js</h1>
<p>最近关注Node.js人变多了。在长期与一堆厚重的Java框架、类库为伍之后,我也想看看外面的世界。Node.js最为人所津津乐道的就是异步加回调机制以及良好的性能。我想知道它和我熟悉的Java有何不同。</p>
<h1>Node.js 要解决的问题</h1>
<p>在使用Java开发的过程里,经常会有与下面类似的代码:</p>
<div class="highlight"><pre><code class="java language-java" data-lang="java"><span class="c1">// block A</span>
<span class="c1">// do something</span>
<span class="c1">// block B</span>
<span class="c1">// on Database</span>
<span class="n">ResultSet</span> <span class="n">rs</span> <span class="o">=</span> <span class="n">dbo</span><span class="o">.</span><span class="na">executeQuery</span><span class="o">(</span><span class="s">"Query Statement"</span><span class="o">);</span>
<span class="k">while</span> <span class="o">(</span><span class="n">rs</span><span class="o">.</span><span class="na">next</span><span class="o">())</span> <span class="o">{</span>
<span class="c1">// block C</span>
<span class="c1">// parse the result</span>
<span class="o">}</span>
<span class="c1">// block D</span>
<span class="c1">// do something</span>
</code></pre></div>
<p>代码块A先处理一些任务;代码块B发送查询语句到数据库,等待返回数据集;代码块C处理返回结果;代码块D继续做其他事情。执行时序图如下:</p>
<p><img src="/resource/2012-09-25/node-js-introduct/1.png" /></p>
<p>容易看出,在等待代码块B时,整个程序都暂停了,中间有一大段空闲时间没有处理任何任务。从依赖关系上说,代码块C必须在代码块B成功执行后才能执行;但代码块D对前面的B、C并没有依赖关系。因此,如果在等待期间先执行代码块D,直到代码块B执行完毕再触发代码块C。如下图所示:</p>
<p><img src="/resource/2012-09-25/node-js-introduct/2.png" /></p>
<p>假设每个代码块所需的执行时间是5秒,那第一种方案需要20秒,而第二种只需要15秒。Node.js要做的事情就是使用第二种方案取代第一种方案以获得性能的提升。</p>
<h1>回调函数队列</h1>
<p>情理之中意料之外,Node.js实现的方式是单进程且单线程。它内部维护着一个回调函数队列,遵循先到先处理的原则逐个执行。让我联想到<a href="http://zh.wikipedia.org/wiki/%E6%89%B9%E5%A4%84%E7%90%86%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F">批处理操作系统</a>,任务一个接一个地执行,没有抢占并享有所有系统资源。Node.js回调机制所做的事情就是把相应的代码块塞到队尾。</p>
<p>比如上一节的例子中的方法二,执行过程就变成:代码块A被塞到队列中;数据库查询语句注册了一个事件并绑定代码块C为回调函数;代码块D被塞到队尾;此时代码块B执行完成并触发事件,把代码块C塞到队尾。因此,依次执行的是A、D、C(注:B是数据库服务器上的查询操作,并不是Node.js中执行的代码),期间并无间歇。</p>
<p>考虑下面的代码,就遵循上述的代码模式:</p>
<ol>
<li>代码块A:请求计数以及记录请求开始处理的时间(不是到达时间)。</li>
<li>代码块B:此处用setTimeout做了5秒延迟,模拟外部程序处理五秒钟。</li>
<li>代码块C:记录回调函数被调用的时间,睡5秒来模拟服务器运算,并记录结束时间。</li>
<li>代码块D:记录响应的时间(即用户收到回馈的时间)。</li>
</ol>
<div class="highlight"><pre><code class="javascript language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">http</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="s1">'http'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">count</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="nx">http</span><span class="p">.</span><span class="nx">createServer</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">request</span><span class="p">,</span> <span class="nx">response</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// block A</span>
<span class="nx">count</span><span class="o">++</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">id</span> <span class="o">=</span> <span class="nx">count</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">start</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">();</span>
<span class="kd">var</span> <span class="nx">reply</span><span class="p">;</span>
<span class="c1">// block B</span>
<span class="nx">setTimeout</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// block C</span>
<span class="kd">var</span> <span class="nx">called</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">();</span>
<span class="kd">var</span> <span class="nx">end</span><span class="p">;</span>
<span class="k">do</span> <span class="p">{</span>
<span class="nx">end</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">();</span>
<span class="p">}</span> <span class="k">while</span> <span class="p">(</span><span class="nx">end</span><span class="p">.</span><span class="nx">getTime</span><span class="p">()</span> <span class="o">-</span> <span class="nx">called</span><span class="p">.</span><span class="nx">getTime</span><span class="p">()</span> <span class="o"><</span> <span class="mi">5000</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">id</span> <span class="o">+</span> <span class="s1">' start @ '</span> <span class="o">+</span> <span class="nx">start</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">id</span> <span class="o">+</span> <span class="s1">' reply @ '</span> <span class="o">+</span> <span class="nx">reply</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">id</span> <span class="o">+</span> <span class="s1">' called @ '</span> <span class="o">+</span> <span class="nx">called</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">id</span> <span class="o">+</span> <span class="s1">' end @ '</span> <span class="o">+</span> <span class="nx">end</span><span class="p">);</span>
<span class="p">},</span> <span class="mi">5000</span><span class="p">);</span>
<span class="c1">// block D</span>
<span class="nx">response</span><span class="p">.</span><span class="nx">writeHead</span><span class="p">(</span><span class="mi">200</span><span class="p">,</span> <span class="p">{</span><span class="s1">'Content-Type'</span><span class="o">:</span> <span class="s1">'text/html'</span><span class="p">});</span>
<span class="nx">response</span><span class="p">.</span><span class="nx">end</span><span class="p">();</span>
<span class="nx">reply</span> <span class="o">=</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">();</span>
<span class="p">}).</span><span class="nx">listen</span><span class="p">(</span><span class="mi">80</span><span class="p">);</span>
</code></pre></div>
<p>在我的机器上执行结果如下:</p>
<div class="highlight"><pre><code class="bash language-bash" data-lang="bash">λ sudo node main.js
1 start @ Fri Sep 21 2012 19:12:35 GMT+0800 <span class="o">(</span>CST<span class="o">)</span>
1 reply @ Fri Sep 21 2012 19:12:35 GMT+0800 <span class="o">(</span>CST<span class="o">)</span>
1 called @ Fri Sep 21 2012 19:12:40 GMT+0800 <span class="o">(</span>CST<span class="o">)</span>
1 end @ Fri Sep 21 2012 19:12:45 GMT+0800 <span class="o">(</span>CST<span class="o">)</span>
</code></pre></div>
<p>和预期的一样:请求在19:12:35时开始处理,并且没有被阻塞,而是在同一时间就返回了;5秒后回调函数开始被处理;又过了5秒回调函数执行完毕,整个过程结束。</p>
<p>我们来数数上面这段代码总共有几个显眼的回调函数:</p>
<ol>
<li>整段代码/文件是一个回调函数,在程序启动时被塞到队列中并立即执行了;</li>
<li>createServer 中注册的回调函数,在收到用户请求后被触发(塞到队列,不一定马上执行);</li>
<li>setTimeout 中注册的回调函数,在延迟5后才被塞到队列。</li>
</ol>
<h1>单线程的问题</h1>
<p>Node.js采用单进程+单线程的其中一个原因是避免系统频繁开辟线程带来的开销。网络上说开启一个线程要使用2M的内存(有这么多?),我没去验证过具体数值,但至少是有一些开销的,当请求量大是的确会成为瓶颈。上节提到Node.js的处理任务的方式类似批处理操作系统,因此它在规避线程开销的同时也完全继承了批处理方式的缺陷——交互不友好。我指的交互是后面的请求会被回调函数队列中前面的任务阻塞住,从接受到回馈耗费很长的时间等待。</p>
<p>用下面代码分别在第0秒、第1秒、第7秒发送一个请求,并记录每个请求从发出到收到回馈的时间:</p>
<div class="highlight"><pre><code class="bash language-bash" data-lang="bash"><span class="c">#!/bin/bash</span>
date
<span class="nb">time </span>curl http://localhost
sleep 1
date
<span class="nb">time </span>curl http://localhost
sleep 6
date
<span class="nb">time </span>curl http://localhost
date
</code></pre></div>
<p>执行结果如下:</p>
<ol>
<li>19:16:36 发送第一个请求,几乎马上收到回馈;</li>
<li>等待1秒后发送第二个请求,同样马上收到回馈;</li>
<li>继续等待6秒发送第三个请求,耗时8秒后猜收到回馈!</li>
</ol>
<div class="highlight"><pre><code class="bash language-bash" data-lang="bash"><span class="nv">$ </span>./submit.sh
2012年 09月 21日 星期五 19:16:36 CST
real 0m0.018s
user 0m0.004s
sys 0m0.008s
2012年 09月 21日 星期五 19:16:37 CST
real 0m0.015s
user 0m0.012s
sys 0m0.000s
2012年 09月 21日 星期五 19:16:43 CST
real 0m7.982s
user 0m0.000s
sys 0m0.004s
2012年 09月 21日 星期五 19:16:51 CST
</code></pre></div>
<p>相信第三次请求的用户会极其不满,来看看Node.js执行时的快照:</p>
<div class="highlight"><pre><code class="bash language-bash" data-lang="bash">1 start @ Fri Sep 21 2012 19:16:36 GMT+0800 <span class="o">(</span>CST<span class="o">)</span>
1 reply @ Fri Sep 21 2012 19:16:36 GMT+0800 <span class="o">(</span>CST<span class="o">)</span>
1 called @ Fri Sep 21 2012 19:16:41 GMT+0800 <span class="o">(</span>CST<span class="o">)</span>
1 end @ Fri Sep 21 2012 19:16:46 GMT+0800 <span class="o">(</span>CST<span class="o">)</span>
2 start @ Fri Sep 21 2012 19:16:37 GMT+0800 <span class="o">(</span>CST<span class="o">)</span>
2 reply @ Fri Sep 21 2012 19:16:37 GMT+0800 <span class="o">(</span>CST<span class="o">)</span>
2 called @ Fri Sep 21 2012 19:16:46 GMT+0800 <span class="o">(</span>CST<span class="o">)</span>
2 end @ Fri Sep 21 2012 19:16:51 GMT+0800 <span class="o">(</span>CST<span class="o">)</span>
3 start @ Fri Sep 21 2012 19:16:51 GMT+0800 <span class="o">(</span>CST<span class="o">)</span>
3 reply @ Fri Sep 21 2012 19:16:51 GMT+0800 <span class="o">(</span>CST<span class="o">)</span>
3 called @ Fri Sep 21 2012 19:16:56 GMT+0800 <span class="o">(</span>CST<span class="o">)</span>
3 end @ Fri Sep 21 2012 19:17:01 GMT+0800 <span class="o">(</span>CST<span class="o">)</span>
</code></pre></div>
<p>下表中A1表示第一次请求中的代码块A,其他以此类推。假定代码块A、B、D都是瞬间完成,只有C耗时5秒。从表中可知,第三次请求是在第15钟才开始被处理。根据测试脚本的输出可知,第三次请求其实在第7秒就已经发出了,但由于是那时队列中还有C1、C2在处理,因此等待了8秒钟!假设队列的平均长度是100,那每个请求平均的等待时间就是 (100 / 4 - 1) * (A+B+C+D),即要等前面24个请求处理完。这还仅仅是请求得到回馈的时间,该请求对应回调函数被执行的时间还要更久。</p>
<table>
<tbody><tr><td>当前时间</td>
<td>当前队列</td></tr></tbody>
<tbody><tr><td>0</td>
<td>A1 B1 D1</td></tr>
<tr><td>1</td>
<td>A2 B2 D1</td></tr>
<tr><td>5</td>
<td>C1</td></tr>
<tr><td>6</td>
<td>C1 C2</td></tr>
<tr><td>7</td>
<td>C1 C2 A3 B3 D3</td></tr>
<tr><td>10</td>
<td>C2 A3 B3 D3</td></tr>
<tr><td>15</td>
<td>A3 B3 D3</td></tr>
<tr><td>20</td>
<td>C3</td></tr>
<tr><td>25</td>
<td><br /></td></tr></tbody></table>
<h1>适用场景</h1>
<p>通过上面的研究,我觉得Node.js并不适合需要与用户实时交互的系统;它适合集中处理用户发来的大规模“指令”,即不需要及时看到结果的请求。比如微博系统,用户发表一条微博,可能需要在服务器上排队1分钟才能最终保存到数据库。在这一分钟里,用户更多地是看看别人发表的微博,并不十分迫切地想看到自己那条微博。如果希望有更好的体验,其实可以用DOM直接把用户发表的微博先更新到当前页面,同时使用Ajax异步请求保存这条数据。</p>
http://zzp.me/2012-08-22/rhino-database-access/
JavaScript通过JDBC访问数据库
2012-08-22T09:20:00+08:00
2012-08-22T09:20:00+08:00
张泽鹏
http://zzp.me/
<p>提起服务器端 JavaScript,很多人第一反应都是 Node.js。其实 Java 6 开始包含 Script Engine,其中就自带了一个“阉割版”的 Mozilla Rhino - 纯 Java 实现的 JavaScript 解释器。</p>
<p>使用 jrunscript 就能启动这个解释器。使用 Rhino 的好处是你能使用 JavaScript 语言做开发,但又能使用现成的浩瀚的 Java 库!而且支持编译成 class 文件。</p>
<p>我以连接 Sqlite 数据库为例子抛砖引玉。首先创建一个 sqlite 数据库:</p>
<div class="highlight"><pre><code class="bash language-bash" data-lang="bash">sqliteλ sqlite3 user.db
SQLite version 3.7.3
Enter <span class="s2">".help"</span> <span class="k">for </span>instructions
Enter SQL statements terminated with a <span class="s2">";"</span>
sqlite> .header on
sqlite> <span class="k">select</span> * from users<span class="p">;</span>
id<span class="p">|</span>name<span class="p">|</span>age
1<span class="p">|</span>Joe<span class="p">|</span>24
2<span class="p">|</span>redraiment<span class="p">|</span>24
3<span class="p">|</span>Kewell<span class="p">|</span>30
sqlite> .quit
sqliteλ
</code></pre></div>
<p>JS 封装 JDBC</p>
<div class="highlight"><pre><code class="javascript language-javascript" data-lang="javascript"><span class="kd">var</span> <span class="nx">with_connection</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">url</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">connection</span> <span class="o">=</span> <span class="nx">java</span><span class="p">.</span><span class="nx">sql</span><span class="p">.</span><span class="nx">DriverManager</span><span class="p">.</span><span class="nx">getConnection</span><span class="p">(</span><span class="nx">url</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">argc</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="nx">argc</span> <span class="o"><</span> <span class="nx">arguments</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">argc</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">arguments</span><span class="p">[</span><span class="nx">argc</span><span class="p">].</span><span class="nx">call</span><span class="p">(</span><span class="nx">connection</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">connection</span><span class="p">.</span><span class="nx">close</span><span class="p">();</span>
<span class="p">};</span>
<span class="kd">var</span> <span class="nx">with_query</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">sql</span><span class="p">,</span> <span class="nx">fn</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">call</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">prepareStatement</span><span class="p">(</span><span class="nx">sql</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">rs</span> <span class="o">=</span> <span class="nx">call</span><span class="p">.</span><span class="nx">executeQuery</span><span class="p">();</span>
<span class="kd">var</span> <span class="nx">meta</span> <span class="o">=</span> <span class="nx">rs</span><span class="p">.</span><span class="nx">getMetaData</span><span class="p">();</span>
<span class="kd">var</span> <span class="nx">list</span> <span class="o">=</span> <span class="p">[];</span>
<span class="k">while</span> <span class="p">(</span><span class="nx">rs</span><span class="p">.</span><span class="nx">next</span><span class="p">())</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">o</span> <span class="o">=</span> <span class="p">{};</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><=</span> <span class="nx">meta</span><span class="p">.</span><span class="nx">getColumnCount</span><span class="p">();</span> <span class="nx">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">o</span><span class="p">[</span><span class="nx">meta</span><span class="p">.</span><span class="nx">getColumnName</span><span class="p">(</span><span class="nx">i</span><span class="p">)]</span> <span class="o">=</span> <span class="nx">rs</span><span class="p">.</span><span class="nx">getObject</span><span class="p">(</span><span class="nx">i</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">list</span><span class="p">.</span><span class="nx">push</span><span class="p">(</span><span class="nx">o</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">rs</span><span class="p">.</span><span class="nx">close</span><span class="p">();</span>
<span class="nx">call</span><span class="p">.</span><span class="nx">close</span><span class="p">();</span>
<span class="k">return</span> <span class="nx">fn</span><span class="p">(</span><span class="nx">list</span><span class="p">);</span>
<span class="p">};</span>
<span class="p">};</span>
</code></pre></div>
<p>使用:读取表信息</p>
<div class="highlight"><pre><code class="javascript language-javascript" data-lang="javascript"><span class="k">new</span> <span class="nx">org</span><span class="p">.</span><span class="nx">sqlite</span><span class="p">.</span><span class="nx">JDBC</span><span class="p">();</span>
<span class="nx">with_connection</span><span class="p">(</span>
<span class="s1">'jdbc:sqlite:user.db'</span><span class="p">,</span>
<span class="nx">with_query</span><span class="p">(</span><span class="s1">'select * from users'</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">rs</span><span class="p">)</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">row</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">row</span> <span class="o"><</span> <span class="nx">rs</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="nx">row</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="k">for</span> <span class="p">(</span><span class="kd">var</span> <span class="nx">columnName</span> <span class="k">in</span> <span class="nx">rs</span><span class="p">[</span><span class="nx">row</span><span class="p">])</span> <span class="p">{</span>
<span class="nx">printf</span><span class="p">(</span><span class="s1">'%s => %s\n'</span><span class="p">,</span> <span class="nx">columnName</span><span class="p">,</span> <span class="nx">rs</span><span class="p">[</span><span class="nx">row</span><span class="p">][</span><span class="nx">columnName</span><span class="p">]);</span>
<span class="p">}</span>
<span class="nx">print</span><span class="p">(</span><span class="s1">'\n'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="p">);</span>
</code></pre></div>
<p>执行结果</p>
<div class="highlight"><pre><code class="bash language-bash" data-lang="bash">sqliteλ jrunscript -cp <span class="s2">"$CLASSPATH:$PWD/sqlite-jdbc-3.7.2.jar"</span> jdbc.js
<span class="nv">id</span> <span class="o">=</span>> 1
<span class="nv">name</span> <span class="o">=</span>> Joe
<span class="nv">age</span> <span class="o">=</span>> 24
<span class="nv">id</span> <span class="o">=</span>> 2
<span class="nv">name</span> <span class="o">=</span>> redraiment
<span class="nv">age</span> <span class="o">=</span>> 24
<span class="nv">id</span> <span class="o">=</span>> 3
<span class="nv">name</span> <span class="o">=</span>> Kewell
<span class="nv">age</span> <span class="o">=</span>> 30
sqliteλ
</code></pre></div>