<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>技术奇异点</title>
	<atom:link href="http://techsingular.net/?feed=rss2" rel="self" type="application/rss+xml" />
	<link>http://techsingular.net</link>
	<description>You are what you program</description>
	<lastBuildDate>Fri, 03 Sep 2010 14:37:16 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>再抽象一点</title>
		<link>http://techsingular.net/?p=1125</link>
		<comments>http://techsingular.net/?p=1125#comments</comments>
		<pubDate>Fri, 03 Sep 2010 14:37:16 +0000</pubDate>
		<dc:creator>Singularity</dc:creator>
				<category><![CDATA[软件开发]]></category>

		<guid isPermaLink="false">http://sipoint.wordpress.com/?p=1125</guid>
		<description><![CDATA[软件开发是控制复杂度的艺术，是『抽象』[1] 的艺术。软件开发者要熟悉和操作各种抽象。无法想像离开进程、套接字（socket）、内存地址空间（address space）、互斥量等等这些抽象如何构建有用的软件系统。但是，也有大量的垃圾抽象，它们声称能带来的好处无法抵消造成的麻烦 [2] ，仅仅当情况和设计者提供的例子最接近甚至完全相同的时候才能隐藏复杂度，而在其它情况下 —— 比如新的 use case ，和第三方库集成，满足性能需求，提高用户体验 [3] ，或者在学习阶段等等 —— 都必须一再让其开发者对本来应该隐藏底层的细节不得不进行深入了解。 优秀的抽象往往也是稳定的。尽管底层系统沧海桑田，优秀的抽象概念不会变化，还会默默地借助底层的发展为开发者提供免费的午餐。而垃圾抽象们每每梢有风吹草动，就要推出一个划时代的新框架，呼吁号召大家抛弃遗产（legacy）。为什么不同抽象的差异这么明显？ 两种方向 Donald Knuth 在《Coder at Works》中解释他设计软件的方法：一方面，有一个总的目标，从总目标开始把问题细化（break down），分别解决每一个问题，在解决的过程中再做进一步的细化，这是自顶向下的方式（top-down）；另一方面，对手头的原始接口 [4] 从直觉和经验出发进行初步加工，简化原始接口的一般复杂性，较少直接考虑最终目标，这是自底向上（bottom-up）。除非是非常简单的问题，几乎不可能通过纯粹的 top-down 或者纯粹的 bottom-up 达到目标。当 top-down 方式被过于频繁的 break down 不断打断，我们就会转入 bottom-up 模式。在 bottom-up 过程中，如果底层接口中显而易见的可简化部分被处理完，进一步的简化方向不能确定，我们就会转入 top-down 模式。Eric Steven Raymond 在那本著名的《The Art of UNIX Programming》里也阐述过类似的理论。 大型通用开发领域的『抽象』基本属于 bottom-up 方式 —— 不直接关注系统的最终目标，集中精力隐藏中间环节的复杂度。而且，在通用软件领域的竞争和协作的大环境中抽象意味着执行 bottom-up 减少接口复杂度和执行 top-down 完成最终目标的是不同的人群。这意味着 [...]]]></description>
			<content:encoded><![CDATA[<p style="text-align:left;">软件开发是控制复杂度的艺术，是『抽象』[1] 的艺术。软件开发者要熟悉和操作各种抽象。无法想像离开进程、套接字（socket）、内存地址空间（address space）、互斥量等等这些抽象如何构建有用的软件系统。但是，也有大量的垃圾抽象，它们声称能带来的好处无法抵消造成的麻烦 [2] ，仅仅当情况和设计者提供的例子最接近甚至完全相同的时候才能隐藏复杂度，而在其它情况下 —— 比如新的 use case ，和第三方库集成，满足性能需求，提高用户体验 [3] ，或者在学习阶段等等 —— 都必须一再让其开发者对本来应该隐藏底层的细节不得不进行深入了解。</p>
<p style="text-align:left;">优秀的抽象往往也是稳定的。尽管底层系统沧海桑田，优秀的抽象概念不会变化，还会默默地借助底层的发展为开发者提供免费的午餐。而垃圾抽象们每每梢有风吹草动，就要推出一个划时代的新框架，呼吁号召大家抛弃遗产（legacy）。为什么不同抽象的差异这么明显？</p>
<h3>两种方向</h3>
<p style="text-align:left;">Donald Knuth 在《Coder at Works》中解释他设计软件的方法：一方面，有一个总的目标，从总目标开始把问题细化（break down），分别解决每一个问题，在解决的过程中再做进一步的细化，这是自顶向下的方式（top-down）；另一方面，对手头的原始接口 [4] 从直觉和经验出发进行初步加工，简化原始接口的一般复杂性，较少直接考虑最终目标，这是自底向上（bottom-up）。除非是非常简单的问题，几乎不可能通过纯粹的 top-down 或者纯粹的 bottom-up 达到目标。当 top-down 方式被过于频繁的 break down 不断打断，我们就会转入 bottom-up 模式。在 bottom-up 过程中，如果底层接口中显而易见的可简化部分被处理完，进一步的简化方向不能确定，我们就会转入 top-down 模式。Eric Steven Raymond 在那本著名的《The Art of UNIX Programming》里也<a href="http://www.faqs.org/docs/artu/ch04s03.html#id2899552" target="_blank">阐述过</a>类似的理论。</p>
<p>大型通用开发领域的『抽象』基本属于 bottom-up 方式 —— 不直接关注系统的最终目标，集中精力隐藏中间环节的复杂度。而且，在通用软件领域的竞争和协作的大环境中抽象意味着执行 bottom-up 减少接口复杂度和执行 top-down 完成最终目标的是不同的人群。这意味着 bottom-up 的执行者必须预测 top-down 的执行者的意图，通过预测后者的行为，降低接口的整体复杂度。而且，应该承认，为了降低接口复杂度，就必须牺牲底层接口的灵活性。不牺牲任何灵活性的抽象是不存在的（除非这个抽象没有降低接口复杂度 —— 失败的抽象，或者底层接口的某些灵活性本来就是画蛇添足）。所以，只有成功的预测才能保证牺牲的值得。</p>
<h3>直接交流</h3>
<p>预测他人行为最好的方式是直接问他要干什么。『抽象』的构建者与很难大范围的与高层开发者直接交流，即是由于交流成本，也是必须避免得到的需求过于分散。如果缩小直接交流的对象范围，最后构建出来的『抽象』往往会成为一个高层项目的附庸，或者专门为特定的需求特殊的小群体（niche）采用。先不谈为 niche 设计的抽象，作为高层项目附庸的抽象重用度不高，基本可以算 top-down 的行为通过重构（refactor）的延伸，但是这种努力绝不算失败，Linux kernel 中经过重构形成的 CPU 抽象接口（ MMU 抽象、SMP 内存同步操作等等 ）是个成功的例子。尤其是考虑到通过其它预测方式创造出来的所谓通用抽象被高层成功接纳的几率，我觉得更应该提倡这种不那么有野心的抽象方式。</p>
<p>相反，有些抽象的设计者有意避免通过直接交流决定抽象的主要概念和功能，从而期望让其抽象保持最大的通用性。比如进程这类抽象适用于整个计算机行业，今天已经很少有计算行为脱离进程这个抽象来完成了（除去一些嵌入式设备）。也几乎不会有网络应用脱离 socket 来开发。但是有更多的避免直接交流而设计出的抽象并不成功。比如，很多公司已经或者正在试图把所有操作系统的 GUI 开发接口统一到单一的抽象（比如 Java Swing/SWT，Adobe AIR，Qt 在 KDE 之外的努力等等）。这些努力拒绝向某个或者某种应用倾斜，而把自己定义为适合绝大多数应用 UI 开发的抽象。就我认识的业界现状，只能说这样的努力都很失败 [5] 。下面谈谈通过直接交流之外的方式设计抽象的成败原因。</p>
<h3><strong>控制行为</strong></h3>
<p>预测一个人更好的方式是规定他要干什么。对于抽象的构建者来说，最好的方式是完全不让高层用户接触底层接口。但是，这样要面对双重压力，第一种压力是开发者无法忍受灵活性的牺牲，要求直接接触底层接口。因为灵活性的牺牲往往意味着不能满足用户的特定需求，特别是那些底层接口可以实现但是由于被高层抽象牺牲灵活性而变成不可能的需求。第二种压力是底层的实现者总是要极力推广自己接口，没有人设计一个接口从一开始就是为了让另一个人来封装的（如果这样最初的设计者就是承认自己的接口设计的很烂），在高层抽象的实现者鼓吹复杂度的降低时，底层实现者也会极力展示那些用高层抽象无法达到的效果。</p>
<p>两种情况可以降低上面的两种压力。第一是底层的提供者为了实现简单，提供的接口复杂度非常高，几乎不考虑解决实际问题的难度，大多数开发者因为过高的复杂度主动避免直接接触底层接口。第二是底层接口的承载体由于成本或者其它原因不能被大多数开发者接触到。高级语言、进程、套接字等等成功地被广泛接受的抽象，在其早期都满足其中一种或者两种情况。但是，这两个缓解压力的情况并不是经常满足的。提供高复杂度接口的行为几乎被限制在了硬件设计行业，PC 等开放平台占据主导地位也让第二种情况变得越来越罕见 [6] 。</p>
<p>并且，在竞争条件下，只要有相当一部分人采用了某个级别的抽象，他们在最终产品的性能和细节上的竞争力往往就会超过采用更高级别抽象的最终产品（假设更高级别抽象做出了性能和灵活性的牺牲）。这样，即使一个更高级别的抽象最初吸引了很多人甚至是相对多数的开发者采用，只要低级别的抽象有一定用户比例，他们的产品的高竞争力也会最终迫使大多数开发者回归。这意味着在竞争条件下，只要有一个开发者对一个抽象施加了第一种压力，他的行为就会迫使其它开发者把这种压力增大几倍。一个抽象成为主流的关键在于早期就要抢占绝大多数低层接口的直接用户，保证不再流失用户。这里的绝大多数不是 51% ，而可能是 85%，95% 甚至更高。而这种快速抢占用户群的情形只有满足上面提到的缓解压力的情况满足的时候才能发生。一个不是非常难用的接口，是不会轻易流失用户的。高级语言在出现之后就很快就让绝大多数开发者 [7] 放弃了直接使用低层的汇编。而之后的 C 语言等比较低级的语言不管有多少缺点，都不会像汇编那样迅速丧失几乎所有的用户，尽管一旦有更高级的新语言出现（特别是背后有大财团的那种），C 就会失去部分开发者，但是随后它们在竞争中总是能和更高级的语言在争夺开发者上进行拉锯。一个抽象概念的提出是应该一击必杀，打持久战就意味着在大型通用开发领域输给了底层接口。</p>
<p>另外，在协作条件下，主流采用的抽象级别也会影响后来者采用的抽象级别。如果你需要的很多第三方支持只提供底层的抽象（比如基于引用计数的直接内存模型），就很难在自己的系统里采用高级抽象（比如纯粹的不带直接内存访问的对象模型）。使用某一种抽象方式的模块越多，就会有更多的模块采用同类的抽象。基本上，90 年代之后，商业软件的开发抽象层次停留在 native 进程（而非 managed 虚拟机）、直接内存模型（有时附带引用计数）、C 语言这一层面上很长时间。类似竞争条件下的那种压力倍增的效应在协作田间下也存在，虽然程度稍低。</p>
<p>以网络通信来看，CORBA 等基于 remote-procedure-call 概念的技术曾经希望以更直观的类似函数调用的方式取代基于 socket 链接的 application-protocol 方式。因为大多数系统仅仅提供对 Socket 完全兼容的实现，最终今天主流的网络开发完全退回到 application-protocol 方式。这是协作和竞争条件共同施加压力的结果。</p>
<p>以编程语言来看，一般只在 C 语言之下的级别才符合缓解压力的两种情况。由于汇编语言等比 C 更低层的抽象过于和机器的实现方式接近，大多数开发者对它们不会有全面的认识，所以少数的编译器和操作系统实现者完全垄断了这种接口的使用。这些少数的接口垄断者可以完全控制上层使用低层的方式，从而可以相对来说比较自由的牺牲某些灵活性而不会受到过多的来自上层开发者的压力。同时，因为都处在被编译器和操作系统将低层接口隔离的境地，上层开发者也不会因为竞争压力而采用更低层的接口。更高级的语言一般建立在 C 之上，有些在操作系统之上还提供虚拟机实现。对于它们来说不幸的是，不管它们声称 C 和操作系统的接口多么难用，这些接口还是还是能被一个本科生基本掌握（相反，汇编和硬件接口对于工作过几年的人来说仍然有一定困难）。所以，不管它们提供了多少便利性，在商业软件开发的竞争和协作中总是会受到直接使用 C 和操作系统的产品的排挤。不管有多少人推广更高级的语言，只要在大型通用开发中有一小部分，比如说 5% 喜欢 C 并且真的能够做出好的产品，他们产生的压力就足以诋毁对更高级语言的推广。更高级语言今天还是只能为专业定制化、企业开发和原型开发广泛采用，因为在这些领域竞争压力和协作的需比商业级开发小得多。</p>
<h3>猜测行为</h3>
<p style="text-align:left;">当你并不甘心依附于某个高层项目，又找不到合适的底层复杂接口，也没有财力去忽悠或者控制别人应该用什么样的平台，剩下的就只有猜测用户对抽象的需求了。遗憾的是你几乎肯定会进入这样的境地：因为你封装的接口并不极端复杂，所以能理解并且直接使用它的人很多，尽管这些人也都希望简化这个接口，但是他们各自希望的简化方式大相径庭，你永远没法讨好所有人、大多数人、甚至没法讨好足够多的人。更糟糕的是，那些一开始喜欢你的抽象的人发现，他们的对手直接用底层接口往往能更精细的实现一个功能，于是开始对你提一些不符合你的抽象设计原则的要求，结果是或者你的抽象开始越来越凌乱，或者在在你拒绝他们的要求之后这些人回到底层接口。</p>
<h3>结论</h3>
<p style="text-align:left;">今天，并不是每个从事计算机开发的程序员都了解关于 MMU，寄存器，缓存级别，以太网这样的知识，但是他们中相当一部分都了解进程、套接字、消息循环、时间驱动这样的概念。这样的一部分人存在于商业软件开发的行业中，即使他们不占相对多数，通过竞争和协作的互动，也足以让比他们熟悉的抽象层次更高的抽象失去主流市场。</p>
<p style="text-align:left;">尽管抽象是控制复杂度的利器，但是大型通用开发领域对于新抽象的接纳是极为抵触的。今天当我们有冲动提出一个新抽象的时候，除非作为长期谨慎的研究行为，应该慎重思考是否应该放弃，或者应该根据现有的抽象概念实现新的可重用组件。除非是原型、临时使用的专用程序、或者为特定行业开发的非通用应用，应该拒绝那些没有占据绝对主导地位的抽象方式。</p>
<p><strong>注释：</strong></p>
<ol>
<li style="text-align:left;">本文只关注面向开发者的中间层次抽象。抽象体现为 API，库，底层服务等形式，同一种抽象可以有不同的具体体现。比如，POSIX 的文件操作和 OS X 的 File Manager 是两种 API，但是它们同样基于无结构文件的创建、读、写、权限的文件抽象。比如，UNIX 和 Windows 对文件的权限设置不同，但是它们同样采用基于 owner，group，everyone 这样的访问权限列表抽象。IMP4 和 POP3 都是采用 application-protocol 这样的抽象概念。</li>
<li style="text-align:left;">同下文，带来的好处和麻烦都是指在大型通用开发的领域。一种技术在开发大型通用应用和开发小型原型中的优势和劣势会有很大区别。一般来说，前者在解决主要问题之外，还要比后者更多的考虑边缘情况（edge case）和长期使用维护成本。</li>
<li style="text-align:left;">性能需求和用户体验在小型原型、企业或者特殊行业开发、临时小型工具中的需求最低。这方面需求的降低往往会连带降低对新 use case 和第三方库集成的需求。</li>
<li style="text-align:left;">『原始』指底层机制提供的接口的高复杂度和低组织结构化。</li>
<li style="text-align:left;">这些跨平台 UI 框架在内部工具开发和原型开发上取得了一些成功，这里的失败是指在大型通用软件市场方面的失败。</li>
<li style="text-align:left;">有趣的是移动设备的开发似乎又提供了这种可能。</li>
<li style="text-align:left;">时间上和今天的技术相比并不快，但是考虑当时的还在 PC 成为大众化产品之前，在计算资源贫乏的情况下当时接纳高级语言的速度是很快的。</li>
</ol>
<p style="text-align:left;">
<p style="text-align:left;">
]]></content:encoded>
			<wfw:commentRss>http://techsingular.net/?feed=rss2&amp;p=1125</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>接受调试器</title>
		<link>http://techsingular.net/?p=1062</link>
		<comments>http://techsingular.net/?p=1062#comments</comments>
		<pubDate>Wed, 18 Aug 2010 12:33:13 +0000</pubDate>
		<dc:creator>Singularity</dc:creator>
				<category><![CDATA[Mac OS X]]></category>
		<category><![CDATA[软件开发]]></category>

		<guid isPermaLink="false">http://sipoint.wordpress.com/?p=1062</guid>
		<description><![CDATA[⋯⋯ 太多时候，用 printf 或者 log 就能发现问题。一些人于是发誓只用这种方法，而且如人所愿，只要时间充裕，力气下足，其实不用 debugger 也能干活。 —— Kernel Programming Guide 十年前刚毕业的时候，在公司里被分配了第一个像样的任务：用 Java 写一个 GUI editor 。准备开发环境的时候遇到一个问题： 对当时的我来说，和 Visual C++ 之类的工具相比，JDK 的 debugger 太难配置了。另一方面，System.out.println 倒是唾手可得的『利器』。最终我完全用后者调试完成了那个 5000 多行的 GUI editor 。之后的七年里我几乎再没有碰过任何 debugger 。对它的全部认识就是设置断点，运行程序 hit 断点之后单步（step in/over）。每次搭建开发环境的时候似乎都会因为因为这样那样的原因而放弃使用 debugger ，printf，println 乃至 log 文件是不二的应对法宝。 直到三年前重新回到 Visual Studio 和 Xcode 的 IDE 环境之后，抱着反正不用费力气配置直接可以用的态度重新拾起 debugger 。下面是大致按照时间顺序的发现： 第一条：call stack 最初七年里无数次从 Java exception [...]]]></description>
			<content:encoded><![CDATA[<blockquote><p>⋯⋯ 太多时候，用 <code>printf</code> 或者 log 就能发现问题。一些人于是发誓只用这种方法，而且如人所愿，只要时间充裕，力气下足，其实不用 debugger 也能干活。</p>
<p style="text-align:right;">—— <a href="http://developer.apple.com/mac/library/documentation/Darwin/Conceptual/KernelProgramming/build/build.html#//apple_ref/doc/uid/TP30000905-CH221-CIHBJCGC" target="_blank"><em>Kernel Programming Guide</em></a></p>
</blockquote>
<p style="text-align:left;">十年前刚毕业的时候，在公司里被分配了第一个像样的任务：用 Java 写一个 GUI editor 。准备开发环境的时候遇到一个问题： 对当时的我来说，和 Visual C++ 之类的工具相比，JDK 的 debugger 太难配置了。另一方面，<code>System.out.println</code> 倒是唾手可得的『利器』。最终我完全用后者调试完成了那个 5000 多行的 GUI editor 。之后的七年里我几乎再没有碰过任何 debugger 。对它的全部认识就是设置断点，运行程序 hit 断点之后单步（step in/over）。每次搭建开发环境的时候似乎都会因为因为这样那样的原因而放弃使用 debugger ，<code>printf</code>，<code>println</code> 乃至 log 文件是不二的应对法宝。</p>
<p style="text-align:left;">直到三年前重新回到 Visual Studio 和 Xcode 的 IDE 环境之后，抱着反正不用费力气配置直接可以用的态度重新拾起 debugger 。下面是大致按照时间顺序的发现：</p>
<h3 style="text-align:left;">第一条：call stack</h3>
<p style="text-align:left;">最初七年里无数次从 Java exception 的 stack trace 里获得解决问题的线索，也在研究 Linux 代码的过程中意识到能随时了解 call stack 的好处，可是我从把 call stack 和 debugger 联系上。重拾 debugger 之后的第一个新认识是它的作用不光是单步，更重要的是可以揭示 call stack 。如果没有 debugger 和 call stack ，阅读已有的 code base 的难度会提高一个数量级。</p>
<h3 style="text-align:left;">第二条：死锁</h3>
<p style="text-align:left;">多线程问题和性能问题被我认为是 debugger 一无用处而非 log 不可解决的难题。直到两年前对着一个debug 状态下僵死的程序做了一个 pause 操作，debugger 显示的 call stack 稳稳当当的停在了死锁的位置。</p>
<h3 style="text-align:left;">第三条：UI 操作</h3>
<p style="text-align:left;">UI 问题被认为是最容易应用 debugger 的，因为一般来说触发断点最直接，而且线程也不多。但是遇到 debugger 会干扰程序本身的 message queue 的时候就束手无策了。为了尽量避免手工加入 logging 代码，也找了各种土办法减少 debugger 对 message queue 的干扰。</p>
<p style="text-align:left;">比如，如果在窗口绘制代码中设置断点，就会让 debugger 在绘制窗口的过程中夺取焦点，continue 之后又会把焦点返回原来的被调试程序的窗口，在 Windows XP 上就会又触发窗口绘制。而 OS X 和 Windows Vista 的 buffered window 能让窗口绘制的代码不会像 Windows XP 里的窗口那样每次在 debugger 把焦点交还的时候都触发窗口绘制。尽量利用不同系统的消息处理差异来找到最合适解决问题的环境。</p>
<p style="text-align:left;">但是这些方法都要靠系统实现的差别和被调试程序逻辑的机缘巧合，并非每次都能成功使用。无法找到一定之规，有的情况也根本没有办法。比如，有些控件会处理失去焦点的事件，用如果在处理失去焦点的代码里加入断点，调试起来就很难分别哪些事件是正常情况下会发出的，哪些是受 debugger 的干扰发出的。</p>
<p style="text-align:left;">困惑了两年多之后终于发现处理 UI 调试的方法其实简单得令人发指。只要能找到纯命令行的 debugger（比如 gdb，Flash 的 fdb），用 ssh 或者 telnet 从另一台机器登录到被调试程序所在的机器，用命令行 debugger 调试就行。这时，任何断点的中断都是通过后台协议传递给 ssh/telnet client 上的终端而不会干扰被调试机器的 message queue 。回顾过去的十年，很惊讶被这个问题困扰了那么长时间的不光是我，还有我询问过的几乎所有同事，几乎每个人处理这种问题都是依靠 log（不得不说有些人，比如志岩还是提出了 remote debugging 的思路，但是很可惜只是一个灵感而不是整套解决方案）。</p>
<h3 style="text-align:left;">第四条：条件断点</h3>
<p style="text-align:left;">如果一个断点的第一次、第二次、⋯⋯ 第 n 次被 hit 的时候你都并不想单步或者检查状态怎么办？当然可以多点几次 debugger 的 continue 按钮，让真正期待的第 n+1 次 hit 到来。但是如果第一次的 hit 会干扰第二次呢？（我听见有人说可以用 ssh+命令行 debugger 调试。不过大多数情况没有必要祭出 remote debugging 。）又或者 n 是 10000 怎么办？为了对付这种情况用了不少土方法，比如临时给被调试的程序代码增加一个分支特地用来设置断点，或者在程序弹出 modal dialog 的时候设置断点。</p>
<p style="text-align:left;">其实 gdb 一类的 debugger 一直都有条件断点的功能。可以让一个断点并不是每次 hit 的时候，而是满足一些额外条件的时候才中断，比如第 n 次被 hit ，或者一个变量大于某个值，或者一个 C 函数的返回值符合条件的时候 ⋯⋯</p>
<h3 style="text-align:left;">第五条：监视变量</h3>
<p style="text-align:left;">你是否曾经大声咒骂过：这个变量到底什么时候被改动的？然后不得不一步步的 step over 查看，发现值发生变化之后，停止调试，重启程序，重复刚才的 use case ，step over 到刚才的地方，step into ，然后再 step over ⋯⋯</p>
<p style="text-align:left;">其实 gdb 和很多 debugger 都能自动监视变量的变化，并且在变化符合某种条件的时候自动中断。条件的配置和条件断点一样灵活。而且 x86 CPU 为了支持这种功能特地设置了几个 debugging resigter （32 位下四个，64 位下更多），所以监视变量几乎不会影响调试速度。不用这个功能都对不起 x86 CPU 上那些专门用来 debug 的硅。</p>
<h3 style="text-align:left;">第六条：Log</h3>
<p style="text-align:left;">最终，还是会有些问题确实需要在不间断的执行中监视某些状态。也就是 log 。可是 log 必须得是在代码里加入 <code>printf</code> 吗？gdb 的 commands 命令让断点在 hit 的时候可以执行一段命令，任何合法的 gdb 命令，比如打印某个变量之后 continue 。</p>
<h3 style="text-align:left;">结论</h3>
<blockquote>
<p style="text-align:left;">&#8230; In many cases, you can find the problem through careful use of <code>printf</code> or IOLog statements. Some people swear by this method, and indeed, given sufficient time and effort, any bug can be found and fixed without using a debugger.</p>
<p style="text-align:right;">—— <em><a href="http://developer.apple.com/mac/library/documentation/Darwin/Conceptual/KernelProgramming/build/build.html#//apple_ref/doc/uid/TP30000905-CH221-CIHBJCGC" target="_blank">Kernel Programming Guide</a></em></p>
</blockquote>
<p style="text-align:left;">接受 debugger 是一个抛弃原有成见的漫长过程。一开始我们总认为自己的土方法是最有效的（因为当我们抱着进入尖端领域的心情开始软件开发的生涯时，周围的前辈总会教你几个土方法）。然后我们知道真的有<strong>先进的工具</strong>。我还远远未了解所有 debugger 的所有功能。但是配置一个 debugger 是将我在所有开发平台上的第一件事。</p>
]]></content:encoded>
			<wfw:commentRss>http://techsingular.net/?feed=rss2&amp;p=1062</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>地址空间划分（一）</title>
		<link>http://techsingular.net/?p=1035</link>
		<comments>http://techsingular.net/?p=1035#comments</comments>
		<pubDate>Mon, 09 Aug 2010 15:03:51 +0000</pubDate>
		<dc:creator>Singularity</dc:creator>
				<category><![CDATA[Mac OS X]]></category>
		<category><![CDATA[软件开发]]></category>

		<guid isPermaLink="false">http://sipoint.wordpress.com/?p=1035</guid>
		<description><![CDATA[研究过 32 位 Linux 内核的人都知道这个内核著名的 3G/1G 划分：低 3G 作为用户态空间，高 1G 作为内核态空间。内核态和用户态共享 4G 的 32 位地址空间。 这个划分成了 32 位内核的基本设计方式。Windows 内核也采用类似的划分（缺省为 2G/2G 划分，可以通过启动参数改为 3G/1G）。所以，可能很多人像我一样，很自然的把这种划分当成了内核的必要设计：内核态和用户态必须共处在同一个地址空间里；似乎如此内核才能管理用户态的内存。见到 Mac OS X 内核 XNU 的设计颠覆了我的观点。XNU 的内核态独占 4G 内核空间，而非与用户态共享。（如下左图显示。） 如果不是因为一个采用如此设计的内核就摆在面前（而且已经在上面工作了三年），我几乎不会想到内核的地址空间还能如此设计。看到这个设计的同时也豁然开朗：一开始认为『内核态和用户态必须共处在同一个地址空间』才能让内核『管理用户态的内存』的想法实在是短路。内核并不能了解用户态如何使用内存，更重要的是，内核并不能信任用户态内存的内容。所以，它根本不能通过和用户态『共处在同一个地址空间』这种方式来管理后者的内存空间。因为『共处在同一个地址空间』提供的唯一功能只是可以直接通过线性地址访问（也就是俗称的裸指针），而内核绝对不能碰用户态的裸指针。内核管理用户态内存的方式只能是通过设置页表和页目录来保证用户态的内存访问的合法性和一些特殊映射。 再者，Linux 和 Windows 下与内核共享地址空间的用户态只能是当前运行的进程。如果是通过系统调用进入内核，还多多少少可以说这时的当前进程和内核的关系比其它进程密切一些。如果是通过中断（时钟或者I/O）进入的内核，这时哪个进程是当前进程对内核来说完全是巧合。更何况无论如何内核也不可能只管理当前进程的内存空间。所以从这个角度说共享地址空间对内核管理用户态内存也没有帮助。 退回到 Linux 和 Windows 的设计，为什么还要让内核与用户态共享地址空间呢。其实是因为尽管每次进入内核态都可能发生进程切换，但是大多数情况下并非一定发生这样的切换。因此，共享地址空间可以避免进出内核态的时候进行地址空间（cr3）的切换。在 x86 构架下，切换地址空间要导致所有 TLB 失效，CPU 必须访问主存更新 TLB。所以，共享地址空间是一个性能 hack，仅此而已。这个性能 hack 如此历史悠久，以至于有些人如我一般完全无法想像还能有其它方式。 不过，XNU 的内核地址空间也并非和用户态空间绝对分离。在每个用户态地址空间的最高几兆也被保留为内核空间（如上右图所示）。这是因为从用户态切换到内核态（通过软中断或者比较新的 x86 syscall 指令）时，并不能同时切换地址空间，所以必须保留这段短短的共享空间来完成地址空间的切换。其实，我们可以把这个设计想像成一个处于 [...]]]></description>
			<content:encoded><![CDATA[<p style="text-align: left;">研究过 32 位 Linux 内核的人都知道这个内核著名的 3G/1G 划分：低 3G 作为用户态空间，高 1G 作为内核态空间。内核态和用户态共享 4G 的 32 位地址空间。</p>
<p style="text-align: left;"><a href="http://techsingular.net/wp-content/uploads/2010/08/2010-0809-KernelUserSplit1.png"><img class="alignnone size-full wp-image-1040" title="2010-0809-KernelUserSplit" src="http://techsingular.net/wp-content/uploads/2010/08/2010-0809-KernelUserSplit1.png" alt="" width="189" height="337" /></a></p>
<p style="text-align: left;">这个划分成了 32 位内核的基本设计方式。Windows 内核也采用类似的划分（缺省为 2G/2G 划分，可以通过启动参数改为 3G/1G）。所以，可能很多人像我一样，很自然的把这种划分当成了内核的必要设计：内核态和用户态必须共处在同一个地址空间里；似乎如此内核才能管理用户态的内存。见到 Mac OS X 内核 XNU 的设计颠覆了我的观点。XNU 的内核态独占 4G 内核空间，而非与用户态共享。（如下左图显示。）</p>
<p style="text-align: left;"><a href="http://techsingular.net/wp-content/uploads/2010/08/2010-0809-44Split1.png"><img class="alignnone size-full wp-image-1042" title="2010-0809-44Split" src="http://techsingular.net/wp-content/uploads/2010/08/2010-0809-44Split1.png" alt="" width="440" height="280" /></a></p>
<p style="text-align: left;">如果不是因为一个采用如此设计的内核就摆在面前（而且已经在上面工作了三年），我几乎不会想到内核的地址空间还能如此设计。看到这个设计的同时也豁然开朗：一开始认为『内核态和用户态必须共处在同一个地址空间』才能让内核『管理用户态的内存』的想法实在是短路。内核并不能了解用户态如何使用内存，更重要的是，内核并不能信任用户态内存的内容。所以，它根本不能通过和用户态『共处在同一个地址空间』这种方式来管理后者的内存空间。因为『共处在同一个地址空间』提供的唯一功能只是可以直接通过线性地址访问（也就是俗称的裸指针），而内核绝对不能碰用户态的裸指针。内核管理用户态内存的方式只能是通过设置页表和页目录来保证用户态的内存访问的合法性和一些特殊映射。</p>
<p style="text-align: left;">再者，Linux 和 Windows 下与内核共享地址空间的用户态只能是当前运行的进程。如果是通过系统调用进入内核，还多多少少可以说这时的当前进程和内核的关系比其它进程密切一些。如果是通过中断（时钟或者I/O）进入的内核，这时哪个进程是当前进程对内核来说完全是巧合。更何况无论如何内核也不可能只管理当前进程的内存空间。所以从这个角度说共享地址空间对内核管理用户态内存也没有帮助。</p>
<p style="text-align: left;">退回到 Linux 和 Windows 的设计，为什么还要让内核与用户态共享地址空间呢。其实是因为尽管每次进入内核态都可能发生进程切换，但是大多数情况下并非一定发生这样的切换。因此，共享地址空间可以避免进出内核态的时候进行地址空间（cr3）的切换。在 x86 构架下，切换地址空间要导致所有 <a href="http://en.wikipedia.org/wiki/Translation_lookaside_buffer" target="_blank">TLB</a> 失效，CPU 必须访问主存更新 TLB。所以，共享地址空间是一个性能 hack，仅此而已。这个性能 hack 如此历史悠久，以至于有些人如我一般完全无法想像还能有其它方式。</p>
<p style="text-align: left;">不过，XNU 的内核地址空间也并非和用户态空间绝对分离。在每个用户态地址空间的最高几兆也被保留为内核空间（如上右图所示）。这是因为从用户态切换到内核态（通过软中断或者比较新的 x86 syscall 指令）时，并不能同时切换地址空间，所以必须保留这段短短的共享空间来完成地址空间的切换。其实，我们可以把这个设计想像成一个处于 micro-kernel 和 monolithic kernel 之间的中间设计。和 micro-kernel 类似，内核也享有独立空间，但是和 monolithic kernel 类似，整个内核都是处在特权级，没有从微内核到内核服务的二次切换。</p>
]]></content:encoded>
			<wfw:commentRss>http://techsingular.net/?feed=rss2&amp;p=1035</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>64 位 Windows 的 32 位用户态</title>
		<link>http://techsingular.net/?p=1017</link>
		<comments>http://techsingular.net/?p=1017#comments</comments>
		<pubDate>Thu, 05 Aug 2010 14:08:54 +0000</pubDate>
		<dc:creator>Singularity</dc:creator>
				<category><![CDATA[软件开发]]></category>

		<guid isPermaLink="false">http://sipoint.wordpress.com/?p=1017</guid>
		<description><![CDATA[上周做了一个 64 位 Windows 的培训。其中一部分是讲 WOW64 。用户态的 32 位代码在即将进入内核态之前会从 x86-64 的 compatibility mode 切换到 long mode（刚刚从内核态返回之后会进行相反的切换）。因为切换是在用户态完成的（通过从 32 位代码段直接 jmp 到 64 位代码段），所以不可能修改 cr3（首级页表的首地址）。 培训的时候没有深究这个问题，后来发觉理解得很模糊。32 位保护模式的 MMU 内存映射是两级，每级页表是 1024 项，每项 32 位。64 位 long mode 的 MMU 映射是四级，每级页表 512 项，每项 64 位。如此如何不修改 cr3 就能做 64 位和 32 位的切换？是在进入内核之后又修改了一次 cr3 ？还是 compatibility mode 可以直接使用 64 位的四级页表？ 这次我发现 [...]]]></description>
			<content:encoded><![CDATA[<p style="text-align: left">上周做了一个 64 位 Windows 的培训。其中一部分是讲 <a href="http://en.wikipedia.org/wiki/WoW64" target="_blank">WOW64</a> 。用户态的 32 位代码在即将进入内核态之前会从 x86-64 的 compatibility mode 切换到 long mode（刚刚从内核态返回之后会进行相反的切换）。因为切换是在用户态完成的（通过从 32 位代码段直接 jmp 到 64 位代码段），所以不可能修改 cr3（首级页表的首地址）。</p>
<p style="text-align: left">培训的时候没有深究这个问题，后来发觉理解得很模糊。32 位保护模式的 MMU 内存映射是两级，每级页表是 1024 项，每项 32 位。64 位 long mode 的 MMU 映射是四级，每级页表 512 项，每项 64 位。如此如何不修改 cr3 就能做 64 位和 32 位的切换？是在进入内核之后又修改了一次 cr3 ？还是 compatibility mode 可以直接使用 64 位的四级页表？</p>
<p style="text-align: left">这次我发现 Google 还是有局限性的。两天里 Google 了十几次也没找到答案，可能是做内核的人觉得这个问题太简单不值一提吧。最后还是老老实实的查了 Intel 的 <em>Architectures Software Developer’s Manual, System Programming Guide</em> ：（9.8.5.3 64-bit Mode and Compatibility Mode Operation）</p>
<blockquote>
<p style="text-align: left"><span style="color: #808080">In compatibility mode, the following system-level mechanisms continue to operate using the IA-32e-mode architectural semantics:</span></p>
<ul style="text-align: left">
<li><span style="color: #808080">Linear-to-physical address translation uses the 64-bit mode extended page-translation mechanism.</span></li>
<li><span style="color: #808080">Interrupts and exceptions are handled using the 64-bit mode mechanisms.</span></li>
<li><span style="color: #808080">System calls (calls through call gates and SYSENTER/SYSEXIT) are handled using </span><span style="color: #808080">the IA-32e mode mechanisms.</span></li>
</ul>
</blockquote>
<p style="text-align: left">这里的 64-bit 模式也就是 AMD 术语里的 long mode ，而 IA-32e mode 是指 CPU 处于 long mode 或者 compatibility 之中任何一种模式。</p>
<p style="text-align: left">所以，compatibility mode 的执行环境有很大一部分是借用 long mode 的模式，因此第一次进入 compatibility mode 之前必须按照 long mode 进行必要的设置（这个设置是在纯 32 位保护模式下关闭 paging 的时候完成的，所以首次进入 compatibility mode 的四级页表只能在低 4G 内存，因为这时只能操作 cr3 的低 32 位）。因为第一次进入 IA-32e 模式一定是在 32 位代码段里进行的，所以 CPU 总是会首先进入 compatibility mode 。</p>
]]></content:encoded>
			<wfw:commentRss>http://techsingular.net/?feed=rss2&amp;p=1017</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>拿到了 CS5</title>
		<link>http://techsingular.net/?p=990</link>
		<comments>http://techsingular.net/?p=990#comments</comments>
		<pubDate>Thu, 15 Jul 2010 13:09:36 +0000</pubDate>
		<dc:creator>Singularity</dc:creator>
				<category><![CDATA[未分类]]></category>

		<guid isPermaLink="false">http://techsingular.net/?p=990</guid>
		<description><![CDATA[最近去欧洲玩了一圈。回来之后是一堆工作和生活上的变动。一直没时间写新东西。 今天拿到了 Adobe Creative Suite 5 。]]></description>
			<content:encoded><![CDATA[<p>最近去欧洲玩了一圈。回来之后是一堆工作和生活上的变动。一直没时间写新东西。</p>
<p>今天拿到了 Adobe Creative Suite 5 。</p>
<p style="text-align: left"><a href="http://techsingular.net/wp-content/uploads/2010/07/2010-0715-CS5.png"><img class="alignnone size-full wp-image-991" src="http://techsingular.net/wp-content/uploads/2010/07/2010-0715-CS5.png" alt="Adobe CS5" width="420" height="591" /></a></p>
]]></content:encoded>
			<wfw:commentRss>http://techsingular.net/?feed=rss2&amp;p=990</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>永远的页</title>
		<link>http://techsingular.net/?p=974</link>
		<comments>http://techsingular.net/?p=974#comments</comments>
		<pubDate>Tue, 15 Jun 2010 04:56:38 +0000</pubDate>
		<dc:creator>Singularity</dc:creator>
				<category><![CDATA[Mac OS X]]></category>

		<guid isPermaLink="false">http://sipoint.wordpress.com/?p=974</guid>
		<description><![CDATA[最近看到有人质疑在今天的电子设备上阅读是否还需要保留『页』这个概念。他们认为『页』是印刷品时代的产物，在电子出版时代除了怀旧没有任何意义，电子出版物已经有了『页』的完美替代品：竖直滚动条。 对我来说，竖直滚动条平滑移动功能的直接后果之一是，在阅读的时候，你会情不自禁地试图避免阅读窗口（或者设备屏幕）边缘处的文字 —— 确实，它们太边缘了，因为没有页只有滚动条的内容没有上下留白，而对这种缺乏留白的逃避会延伸到你正在阅读的那四五行以外的一切文字，从而会无时无刻不让你希望把自己正在看的句子调整到窗口（屏幕）的正中间。于是窗口（屏幕）的大小失去了意义，读者的视界实际上被压窄到四五行。阅读的动作成了以句子为频度的『读 —— 滚动 —— 读』（甚至不是有阅读器还发明了自动卷动功能么）。竖直滚动条成了一个分散注意力的障碍而非方便的阅读工具。更糟糕的是，有了滚动条，文章的编辑者更难把握读者会看到什么样的页面布局，所以很多时候他们也就放弃了这样的思考。结果就是我经常在阅读的时候实在搞不好把一幅插图放到窗口的什么位置最协调（通常我发现最好的位置是赶紧把这个图片滚出窗口）。这实在是比『页』糟糕很多的阅读体验。 我很欣赏 Kindle for Mac 的做法：根据当前窗口大小把窗口的内容区域作为一页，不提供竖直滚动条，只提供翻页操作。因为没有滚动条，这是让我分心最少的一款阅读器。同时根据窗口大小动态的定义页的大小，也避免了在电子设备上照搬印刷品布局的古怪体验。我猜想在 iPad 和 iPhone 上的 Kindle 和 iBooks 也是类似的操作，因为没有窗口分割，所以页面的大小能更简单的固定为设备屏幕的大小 —— 但是 Kindle 没有进入 China App Store 所以我没有用过其 iPhone 版，也没有机会深入使用 iPad。 回顾一下，其实『页』这个概念还真的不一定就是印刷品时代仅仅因为技术局限而留下的遗产。古代人类不就使用过所谓的『卷轴』么？倘若假以改进，『卷轴』印刷品的便携性和不考虑到阅读注意力的单纯操作性不一定就输给『页』。所以我想，在印刷品时代，『页』并不是没有竞争者的无奈选择，而是在选择中胜出的更优秀方案。到了电子出版时代，人类历史依然会不断掀开新的一页。]]></description>
			<content:encoded><![CDATA[<p style="text-align:left;">最近看到有人质疑在今天的电子设备上阅读是否还需要保留『页』这个概念。他们认为『页』是印刷品时代的产物，在电子出版时代除了怀旧没有任何意义，电子出版物已经有了『页』的完美替代品：竖直滚动条。</p>
<p style="text-align:left;">对我来说，竖直滚动条平滑移动功能的直接后果之一是，在阅读的时候，你会情不自禁地试图避免阅读窗口（或者设备屏幕）边缘处的文字 —— 确实，它们太边缘了，因为没有页只有滚动条的内容没有上下留白，而对这种缺乏留白的逃避会延伸到你正在阅读的那四五行以外的一切文字，从而会无时无刻不让你希望把自己正在看的句子调整到窗口（屏幕）的正中间。于是窗口（屏幕）的大小失去了意义，读者的视界实际上被压窄到四五行。阅读的动作成了以句子为频度的『读 —— 滚动 —— 读』（甚至不是有阅读器还发明了自动卷动功能么）。竖直滚动条成了一个分散注意力的障碍而非方便的阅读工具。更糟糕的是，有了滚动条，文章的编辑者更难把握读者会看到什么样的页面布局，所以很多时候他们也就放弃了这样的思考。结果就是我经常在阅读的时候实在搞不好把一幅插图放到窗口的什么位置最协调（通常我发现最好的位置是赶紧把这个图片滚出窗口）。这实在是比『页』糟糕很多的阅读体验。</p>
<p style="text-align:left;">我很欣赏 Kindle for Mac 的做法：根据当前窗口大小把窗口的内容区域作为一页，不提供竖直滚动条，只提供翻页操作。因为没有滚动条，这是让我分心最少的一款阅读器。同时根据窗口大小动态的定义页的大小，也避免了在电子设备上照搬印刷品布局的古怪体验。我猜想在 iPad 和 iPhone 上的 Kindle 和 iBooks 也是类似的操作，因为没有窗口分割，所以页面的大小能更简单的固定为设备屏幕的大小 —— 但是 Kindle 没有进入 China App Store 所以我没有用过其 iPhone 版，也没有机会深入使用 iPad。</p>
<p style="text-align:left;"><a href="http://sipoint.files.wordpress.com/2010/06/kindle.png"><img class="alignnone size-full wp-image-986" title="Kindle" src="http://sipoint.files.wordpress.com/2010/06/kindle.png" alt="" width="400" height="324" /></a></p>
<p style="text-align:left;">回顾一下，其实『页』这个概念还真的不一定就是印刷品时代仅仅因为技术局限而留下的遗产。古代人类不就使用过所谓的『卷轴』么？倘若假以改进，『卷轴』印刷品的便携性和不考虑到阅读注意力的单纯操作性不一定就输给『页』。所以我想，在印刷品时代，『页』并不是没有竞争者的无奈选择，而是在选择中胜出的更优秀方案。到了电子出版时代，人类历史依然会不断掀开新的一页。</p>
]]></content:encoded>
			<wfw:commentRss>http://techsingular.net/?feed=rss2&amp;p=974</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Dict Mac 的发音功能</title>
		<link>http://techsingular.net/?p=945</link>
		<comments>http://techsingular.net/?p=945#comments</comments>
		<pubDate>Tue, 08 Jun 2010 14:29:16 +0000</pubDate>
		<dc:creator>Singularity</dc:creator>
				<category><![CDATA[Mac OS X]]></category>
		<category><![CDATA[软件开发]]></category>

		<guid isPermaLink="false">http://sipoint.wordpress.com/?p=945</guid>
		<description><![CDATA[对于词典软件来说，发音是个必需具备的基本功能，而且 dict.cn 的 Web API 提供了 MP3 格式的单词（短语）发音，所以从开始写 Dict Mac 的起，发音就被列入计划加入的功能。0.02 版完成之后，也就是从 5 月 25 日左右开始准备实现发音功能。 最初的问题是找到合适的 API，花了一个晚上，从 blog 和论坛的帖子堆里发现无数名称，逐一在 Apple 的文档里查找印证，最后终于锁定 Audio Queue Service 这个 framework。有了 framework 的确切名称后就不必再流连于 Stackoverflow 之类的网站，开始专心阅读 Apple 的文档。 花了一个晚上草草看了一下《Audio Queue Service Programming Guide》，感觉不是很好。这个 framework 功能比较强大，设计得比较灵活，相对来说也就没有提供傻瓜化的播放 API，即使是播放一个 MP3 文件也要 400 多行代码。好在这些代码在 programming guide 里都已经直接给出。但是一来我不喜欢这种 copy/paste 代码的方式，二来如果这些代码有 bug（当然 programming guide 里的代码本身有 bug 的可能性不大，但是和 [...]]]></description>
			<content:encoded><![CDATA[<p style="text-align:left;">对于词典软件来说，发音是个必需具备的基本功能，而且 dict.cn 的 Web API 提供了 MP3 格式的单词（短语）发音，所以从开始写 <a href="http://sipoint.wordpress.com/dict-mac/">Dict Mac</a> 的起，发音就被列入计划加入的功能。0.02 版完成之后，也就是从 5 月 25 日左右开始准备实现发音功能。</p>
<p style="text-align:left;">最初的问题是找到合适的 API，花了一个晚上，从 blog 和论坛的帖子堆里发现无数名称，逐一在 Apple 的文档里查找印证，最后终于锁定 <a href="http://developer.apple.com/mac/library/documentation/MusicAudio/Conceptual/AudioQueueProgrammingGuide/Introduction/Introduction.html" target="_blank">Audio Queue Service</a> 这个 framework。有了 framework 的确切名称后就不必再流连于 Stackoverflow 之类的网站，开始专心阅读 Apple 的文档。</p>
<p style="text-align:left;">花了一个晚上草草看了一下《Audio Queue Service Programming Guide》，感觉不是很好。这个 framework 功能比较强大，设计得比较灵活，相对来说也就没有提供傻瓜化的播放 API，即使是播放一个 MP3 文件也要 400 多行代码。好在这些代码在 programming guide 里都已经直接给出。但是一来我不喜欢这种 copy/paste 代码的方式，二来如果这些代码有 bug（当然 programming guide 里的代码本身有 bug 的可能性不大，但是和 Dict Mac 的 code base 集成的时候很可能引入问题），也要花不少时间解决，而且很可能要重读 programming guide 甚至查阅 API reference。</p>
<p style="text-align:left;">另外的问题是选择把 dict.cn 的 MP3 文件 download 到本地文件系统之后再打开文件播放的方式，还是直接播放网络的流数据的方式。从尽量减小暴露给用户的复杂度的角度说，稍稍倾向后者。但是考虑到今后还要实现本地缓存，以及尽早实现功能，还是决定先写出把 MP3 文件按照单词作为索引存储在本地文件系统的代码。花了两个晚上写好了这些代码后，第二天白天我脑子里突然冒出一个问题：OS X 会不会有现成的播放 MP3 的命令行工具？从以往的经验看，OS X 的命令行工具还是相当强大的。Google 了一下，果然有一个 afplay。这样一下子把实现发音功能的剩余工作从至少两个晚上变成了一个小时 —— 只要 fork 一个新进程运行 afplay 即可。</p>
<p style="text-align:left;">其实刚开始写 Dict Mac 的时候就考虑过是不是把必要的 UI 之外的其它功能都做成一个后端独立进程。最后决定当前阶段还是写成一个进程。不过 afplay 至少让发音功能成为了一个独立的进程。一开始我还在抱怨为什么 Mac OS X 不能直接提供一个播放 MP3 的傻瓜接口，其实这样的傻瓜功能应该做成工具而不是 API，像 Audio Queue Service 那样灵活的接口才值得作为 API 的形式提供。这个事情上 OS X 继承了 UNIX 的精髓，也是像 Windows 这样的不管底层功能还是傻瓜功能一股脑的做成 API 的非 UNIX 类的 OS 最缺乏的文化。</p>
]]></content:encoded>
			<wfw:commentRss>http://techsingular.net/?feed=rss2&amp;p=945</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Git、P4merge 和 OS X</title>
		<link>http://techsingular.net/?p=922</link>
		<comments>http://techsingular.net/?p=922#comments</comments>
		<pubDate>Thu, 27 May 2010 15:02:54 +0000</pubDate>
		<dc:creator>sipoint</dc:creator>
				<category><![CDATA[Mac OS X]]></category>
		<category><![CDATA[软件开发]]></category>

		<guid isPermaLink="false">http://sipoint.wordpress.com/?p=922</guid>
		<description><![CDATA[Diff 是程序员最重要的工具之一。不过，除了在 mail 里发送 patch，我很难忍受传统 diff 的字符输出。在任何系统上开发软件，第一件重要的准备工作就是寻找趁手的 GUI diff 工具。GUI diff 工具的优势在于 side-by-side 的比较方式即能显示变化的部分，又不影响对每个版本的连续阅读。﻿Linux 上比较好的是 KDE 的 kompare，也是我见到的第一种 GUI diff 工具。因为先入为主的看到 kompare 使用曲线来联系变化的对应关系，所以任何使用空行填充或者直线来显示对应关系的 GUI diff 工具对我来说也是比较勉强的 —— 可以暂时使用，但是仍然会不断寻找替代品。 平时工作用 Perforce 管理代码，它有个自带的 GUI diff 工具 P4merge。功能很强大（而且免费，虽然 Perforce 是商业软件，但是它的 client，包括 P4v 和 P4merge 都是免费的，而且 P4merge 可以完全脱离 Perforce server 独立使用），除了普通的比较两个文件（版本）之外还可以 3-way 比较（一般用来做人工的 branch merge，分别比较 branch fork 之前的原始版本，main branch [...]]]></description>
			<content:encoded><![CDATA[<p style="text-align:left;">Diff 是程序员最重要的工具之一。不过，除了在 mail 里发送 patch，我很难忍受传统 diff 的字符输出。在任何系统上开发软件，第一件重要的准备工作就是寻找趁手的 GUI diff 工具。GUI diff 工具的优势在于 side-by-side 的比较方式即能显示变化的部分，又不影响对每个版本的连续阅读。﻿Linux 上比较好的是 KDE 的 kompare，也是我见到的第一种 GUI diff 工具。因为先入为主的看到 kompare 使用曲线来联系变化的对应关系，所以任何使用空行填充或者直线来显示对应关系的 GUI diff 工具对我来说也是比较勉强的 —— 可以暂时使用，但是仍然会不断寻找替代品。</p>
<p style="text-align:left;">平时工作用 Perforce 管理代码，它有个自带的 GUI diff 工具 P4merge。功能很强大（而且免费，虽然 Perforce 是商业软件，但是它的 client，包括 P4v 和 P4merge 都是免费的，而且 P4merge 可以完全脱离 Perforce server 独立使用），除了普通的比较两个文件（版本）之外还可以 3-way 比较（一般用来做人工的 branch merge，分别比较 branch fork 之前的原始版本，main branch 上的修改版本，和其它 branch 上的修改版本）。和 editor 不同的是，diff 工具操作的不仅仅是 check-out 的 version，而是 version repository 里的任意版本。所以 diff 工具要想和 version control 系统配合，简单的文件操作是不够的，一个成熟的 version control 系统必须在设计中考虑如何与外部的 diff 工具协作。</p>
<p style="text-align:left;">从上周开始用 Git 管理 Dict Mac 的 code base。用到昨天终于感到缺省的『 <span style="font-family:'Courier New';font-size:12px;">git diff </span>』不好用（类似『 <span style="font-family:'Courier New';font-size:12px;">diff -u</span> 』的输出）。简单的 Google 一下就找到了答案：</p>
<blockquote><p><span style="font-family:'Courier New';font-size:12px;">git difftool &#8230;</span></p></blockquote>
<p style="text-align:left;">原本以为只有在 Linux 上才会有缺省配置好的 GUI diff 工具和 Git 协作。在 Mac 上试过发现 OS X 上已经配置好了 opendiff 可以让 Git 直接使用。OS X 的 out-of-box 用户体验确实不是只用来蒙初学者的。连 developer 的工具也能准备齐全。但我还是最习惯 P4merge。于是试着配置：</p>
<blockquote>
<p style="text-align:left;"><span style="font-family:'Courier New';font-size:12px;">git config &#8211;global diff.tool p4merge<br />
git config &#8211;global \<br />
difftool.p4merge.cmd /Applications/p4merge.app/Contents/MacOS/p4merge</span></p>
</blockquote>
<p style="text-align:left;">然后运行『 <span style="font-family:'Courier New';font-size:12px;">git difftool</span> 』，P4merge 是调用起来了，但是没有内容。原来 Git 缺省不会向 diff 工具传递参数，必须写到命令行配置里：</p>
<blockquote>
<p style="text-align:left;"><span style="font-family:'Courier New';"><span style="font-family:Helvetica;"><span style="font-family:'Courier New';font-size:12px;">git config &#8211;global \<br />
&#8220;difftool.p4merge.cmd \<br />
/Applications/p4merge.app/Contents/MacOS/p4merge \&#8221;\$LOCAL\&#8221; \&#8221;\$REMOTE\&#8221;"</span></span></span></p>
</blockquote>
<p style="text-align:left;">这里还有一个陷阱，在某些 shell 里，『 $ 』是不用加转义的。不过在 OS X 的 bash 里必须加。</p>
<p style="text-align:left;"><img title="P4mergeOnGit.png" src="http://sipoint.files.wordpress.com/2010/05/p4mergeongit.png" border="0" alt="P4mergeOnGit.png" width="420" height="281" /></p>
]]></content:encoded>
			<wfw:commentRss>http://techsingular.net/?feed=rss2&amp;p=922</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Dict Mac 到 Mac OS X 10.5</title>
		<link>http://techsingular.net/?p=912</link>
		<comments>http://techsingular.net/?p=912#comments</comments>
		<pubDate>Tue, 25 May 2010 15:22:57 +0000</pubDate>
		<dc:creator>Singularity</dc:creator>
				<category><![CDATA[Mac OS X]]></category>
		<category><![CDATA[软件开发]]></category>

		<guid isPermaLink="false">http://sipoint.wordpress.com/?p=898</guid>
		<description><![CDATA[把 Dict Mac 介绍给同事们之后遇到最多的回应是『怎么是 10.6 only 的？』我喜欢用最新的东西，加上在家里测试 10.5 也并不方便，所以开发的时候没多想就选了 10.6 SDK。没想到连公司同事这些 Mac 的重量级用户还有不少没升级到 10.6，于是今天中午花了一个小时把最低平台需求降到了 10.5。其间遇到了几个小问题。 第一是 NSApplication 的 delegate 在 10.5 之下没有类型，在 10.6 下则正式定义为 NSApplicationDelegate 接口。不过真正起作用的 selector 都没有变化。把 delegate 的基类从 NSObject &#60;NSApplicationDelegate&#62; 变成 NSObject 之后就解决了问题。第二个问题是 NSTextView 的背景色设置成 [NSColor clearColor] 在 10.6 下工作的很好，到了 10.5 下就变成了黑色，变成 [NSColor windowBackgroundColor] 才行。虽然效果相同，但是从正常思路来说我是希望让控件的背景透明，而不是让它『碰巧』和窗口同色，而且遇到那些背景色可以在运行的时候定制的窗口，非透明的控件还要多写代码来同步颜色。 上面两个问题解决之后，Dict Mac 就可以正常的用 10.5 SDK 编译了，在 OS X [...]]]></description>
			<content:encoded><![CDATA[<p style="text-align:left;">把 Dict Mac 介绍给同事们之后遇到最多的回应是『怎么是 10.6 only 的？』我喜欢用最新的东西，加上在家里测试 10.5 也并不方便，所以开发的时候没多想就选了 10.6 SDK。没想到连公司同事这些 Mac 的重量级用户还有不少没升级到 10.6，于是今天中午花了一个小时把最低平台需求降到了 10.5。其间遇到了几个小问题。</p>
<p style="text-align:left;">第一是 <code>NSApplication</code> 的 delegate 在 10.5 之下没有类型，在 10.6 下则正式定义为 <code>NSApplicationDelegate</code> 接口。不过真正起作用的 selector 都没有变化。把 delegate 的基类从<code> NSObject &lt;NSApplicationDelegate&gt;</code> 变成 <code>NSObject</code> 之后就解决了问题。第二个问题是 <code>NSTextView</code> 的背景色设置成 <code>[NSColor clearColor]</code> 在 10.6 下工作的很好，到了 10.5 下就变成了黑色，变成 <code>[NSColor windowBackgroundColor]</code> 才行。虽然效果相同，但是从正常思路来说我是希望让控件的背景透明，而不是让它『碰巧』和窗口同色，而且遇到那些背景色可以在运行的时候定制的窗口，非透明的控件还要多写代码来同步颜色。</p>
<p style="text-align:left;">上面两个问题解决之后，Dict Mac 就可以正常的用 10.5 SDK 编译了，在 OS X 10.6.2 上运行也一切正常。之后遇到的问题最离奇，程序在 OS X 10.5 上启动后就会崩溃，crash log 报告 selector <code>[DictWindow awakeFromNib]</code> 不存在。Google 之后发现遇到类似问题的人不少，唯一找到的 workaround 是把 <code>[DictWindow awakeFromNib]</code> 里的 <code>[super awakeFromNib]</code> 去掉（完全莫名其妙的改法，这是一个人记得刚刚升级到 Xcode 3.2 的时候并没有这个问题，然后从自己的 version control 找到当时的代码才得到的解决方案）。﻿</p>
<p style="text-align:left;">降级 SDK 总会遇到一些小麻烦。也能从中看到 SDK 的每次升级都确实给开发工作减轻了不少负担，唯一遗憾的是用户们升级得没有那么快，新 SDK 的推出带来的好处要过上一段不短的时间才能让开发者受益。</p>
]]></content:encoded>
			<wfw:commentRss>http://techsingular.net/?feed=rss2&amp;p=912</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Dict Mac 发布</title>
		<link>http://techsingular.net/?p=907</link>
		<comments>http://techsingular.net/?p=907#comments</comments>
		<pubDate>Sat, 22 May 2010 10:30:38 +0000</pubDate>
		<dc:creator>Singularity</dc:creator>
				<category><![CDATA[软件开发]]></category>

		<guid isPermaLink="false">http://sipoint.wordpress.com/?p=893</guid>
		<description><![CDATA[很多天没有更新 Blog 了，其间也有曾经想写些话题，不过大部分时间还是用来动手做被一件很久之前就想做的事情 —— 自己写个小工具。第一个完成的工具是我每天都离不开的 dict.cn 的客户端 Dict Mac。目前刚刚放到网上的只是 0.01 版。功能还不是很多。不过我自己已经完全切换到 Dict Mac 而不再打开 dict.cn 了。 从开发的角度说，这次也是我学习 Cocoa 的第一个实践项目。Cocoa 的 Interface Builder，以及控件对 URL 和 RTF 格式的处理都很好用。]]></description>
			<content:encoded><![CDATA[<p style="text-align: left;">很多天没有更新 Blog 了，其间也有曾经想写些话题，不过大部分时间还是用来动手做被一件很久之前就想做的事情 —— 自己写个小工具。第一个完成的工具是我每天都离不开的 dict.cn 的客户端 <a href="http://techsingular.net/?page_id=866">Dict Mac</a>。目前刚刚放到网上的只是 0.01 版。功能还不是很多。不过我自己已经完全切换到 Dict Mac 而不再打开 dict.cn 了。</p>
<p style="text-align: left;">从开发的角度说，这次也是我学习 Cocoa 的第一个实践项目。Cocoa 的 Interface Builder，以及控件对 URL 和 RTF 格式的处理都很好用。</p>
]]></content:encoded>
			<wfw:commentRss>http://techsingular.net/?feed=rss2&amp;p=907</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
	</channel>
</rss>
