2013年8月14日星期三

昨日的世界(三)

tablization
2013-02-06
在公司加班到3点多,回家睡意全无,当年通宵写代码,通宵写论文,通宵看博客,通宵看视频,通宵看书,通宵开心快乐,通宵黯然神伤,各种通宵的那种感觉似曾相识.角色切换,一介码农,所思所想唯有代码.
连续写了3个多月写lua和c#代码,到最好都代码疲劳了,不过我总体来说还是控制住了代码的腐烂速度,没有变的不可收拾,这主要是得益于坚持随时随地的重构工作.尤其是最近这种探索性带有点个人创造性的工作被任务压力紧堆着的时候,总是容易写出一些烂代码出来.可见自己功力还不够,道行还不够深,希望明年内能深入体会和实践kiss原则,唯有内化了的kiss原则才能让自己的代码保持精简而结实.否则的话低级偷懒所带来的重构和测试成本是很高的.
应题,我想要讨论的主要是tab,是的,tab.你正在用tab化的浏览器看我这篇文章,我这篇文章也是在tab化的后台里写的.整个计算机UI史几乎就是在有限屏幕里各种变形tab进行乾坤大挪移的历史.无论是windows的任务栏,还是mac的悬浮Dock,无论是各种客户端软件还是各种网站设计,几乎都是在进行tab的设计.设计好tab,组织和管理好tab,美化好tab,这是UI里最重要的一个环节.设计和实现tab应该要重点考虑哪些要素呢?

以我所见,至少有以下几点需要考虑周到.
1.tab的独立.
2.tab的同步.
3.tab的嵌套.
4.tab的跟随.
5.tab的变形.
6.tab的层次.
7.tab的一致.
8.tab的排斥.
暂时能想到的是这些,有空的话会更仔细琢磨.想要掌控UI,就得不断思考和改进.做UI的程序不能被tab玩死,要玩活tab.我最佩服和欣赏的tab是看不见的tab,存在于你的指尖之下.那就是,the vim editor,vim的有模式编辑,在我看来就是一种顶级的tab,你看不见,不过你却切切实实感受到它的存在,它的犀利,正如命令行是黑客的tab利器.
不过,写tab是很蛋疼的,每次我写到郁闷的时候,就发誓"以后我再也不写tab式软件了!",至少以前跟舍友一起讨论的时候,我们都很唾弃"PPT式"软件.是的,PPT式软件,实际上就是Tab式软件,众观IT世界,PPT式软件无所不在,无所不能,无论什么东西,它都能用PPT给你装进去.PPT是伟大的发明,然而也是偷懒的发明.我们都知道好的PPT和差的PPT会差好几个档次,PPT本身只是一种事物的组织方式,如何用这种组织方式把你的内容,条清缕析,引人入胜的进行组织是一门学问.多思考这些,我们就会发现生活,离不开排版.每次见排版高手们的作品,都很喜欢.不过说到排版,排版式思想只是做UI的一种方法,有其优点亦有缺点,不好的排版策略,会成为"智能的就是傻逼的".比如早期的word.比如LaTeX的图片排版,真是蛋疼.容我吐槽,我现在觉的TeX的精髓应该是在TeX内核本身,而绝不是LaTeX.使用LaTeX并不能带给你排版的真正自由,我觉的TeX内核+宏机制才是能真正带给你自由排版的东西.而不是有限定制的LaTeX.这跟UI一样,只会拖拉控件做UI的永远无法接触到UI的精髓.
今天先写到这,下次,我想谈谈"老头滚动条",或者"上古卷轴".old scroll.

while switch
2013-01-27
2012年写的代码,还是单机版偏多,很多比较核心的模块代码,写起来后发现软件工程神马的都可以去死了,本质上最最最最有趣的居然就是while-switch.while表示要不断去做,switch表示状态切换.与while-switch相伴的几个词是:queue,productor-consumer,thread,try;lock;...
[01].win32消息循环,是一个while-switch,是一个典型的productor-consumer;消息就是个队列.消息循环可以嵌套,内层消息循环和外层消息循环都可以去消费message.message并不只属于外层!程序即数据,把消息即ID.
[02].分组线程池,可分组的线程池;我们本质上也是做成一个productor-consumer,每个threadpool的workitemsgroup就是一个productor,每个threadpool的workitemrunner就是一个consumer;
[03].服务推送,也是一个while-switch.本地应用while监听服务器消息,switch分发给处理者.自动更新只是一个consumer行为.进程间通讯,更多的是互相推送和监听.thrift和WCF都对底层实现做了封装.WCF的概念定义比thrift更具学理:A,B,C.其中A是Adress,通讯的地址;B是Binding,绑定的协议;C是Concat,服务-数据-策略的约定.
[04].下载行为,隐性的while,显性的switch-case;一个典型的有限状态机,下载的行为与状态,界面的更新等等都是很常见的UI场景,这块逻辑一开始写都是比较随意的打补丁,多做几次自然可以在以后新写的时候有干净清爽的设计和实现.
[05].switch-case的关键词是状态,case的是状态的唯一ID.实际编程中很多地方都本质上是用唯一ID定位的,windows下有一个名词叫handle,这东西跟message一样,本质上就是唯一ID.另一个例子是在OOP宿主语言里注册对象给Lua,本质上就是注册唯一ID给Lua就可以.唯一ID要看上下文,唯一并非要全局唯一,有时候只要局部唯一就可以.
[06].while的退出,win32message的例子是发送一个WM_QUIT的消息去结束while.如果是一个异步循环操作,则可以通过一个简单的bool变量在每次循环执行时判断下是否退出,本质上也就是一个if-else.
[07].while的阻塞,win32message的例子是内部的死循环弹出模态对话框.异步操作的例子,是通过每次操作都加锁,需要阻塞时在其他线程获取锁,一旦获取到锁即可阻塞掉循环的下一次执行.

a few tips 4 program
2013-01-27
program是一种技术&艺术.<人月神话>说,没有银弹.对于真正的program来说:向下不断接近硬件,理清大厦的基石如何层层层层构建而来;向上不断面向用户,通过抽象-封装-组合-重构-迭代创造虚拟世界.program有狭义,广义之分:失之狭义,无法享受实做带来的深刻理解和快乐;失之广义,无法由里及外地迁移知识迭代人生.小而庖丁解牛,大而胸怀天下,program的本质在于smart.
1.UML图,流程图,文档本质上都应该是草稿,不是教条,不是目标.是教条是目标是标准化产品;是草稿是脚手架是技艺之结晶.
2.小而精,是为美;大而清,是为良;
3.保持简单,可组合.
4.小偷懒,大成本.
5.简单可并行优于复杂单线程.
6.螳螂捕蝉,黄雀在后.事情往往没那么简单.
7.折腾使人进步,焦虑使人智慧.
8.自由创造.

代码整洁之道
2013-03-09
摘要
本贴记录日常开发中所使用的各种代码整洁技术。在日常开发中,我们需要随时发现重构点,并通过重构使代码保持整洁,从而达到代码的易于阅读、易于维护的目的。开发者总喜欢使用各种其他行业的东西对编程本身做各种隐喻。譬如「建筑」、「写作」、「工程」等等。虽然有的程序员反对这种隐喻,认为编程本身的概念解释就足以阐明程序之道,但软件工程从一开始就借助这些隐喻达到对并不成体系的软件思想的描述和传播。阅读这类软件工程的书,并内化软件工程思想是可以让程序员从普通到优秀,从优秀到卓越。具体来说,有《人月神话》、《代码大全》、《程序员修炼之道-从小工到专家》、《代码整洁之道》、《重构-改善既有代码的设计》、《设计模式-可复用面向对象软件的基础》等等。回顾上述的各种隐喻,我认为用「写作」来对编程做某些方面的隐喻是很恰当的。写作意味着谋篇布局,纲举目张,乾坤大挪移;写作意味着大量阅读优秀作品,不断实践并内化各种软件工程思想,并最终创新作品;写作意味着随时积累小片段,蚂蚁搬家,积少成多,厚积薄发。正是基于上述理由,我认为有必要将日常开发中的小片段条分缕析,归档供查。本贴即是专门积累「代码整洁」相关的小碎片,同时实践“从小工到专家”的思想。

[bolt#]滚动条
2013-02-23
一、折腾。
  今天折腾了一整天没搞定一个问题。下班的时候特别郁闷地发现自己犯了个错误,白白浪费了时间。晚上回来深刻反省,突然明白了很多东西。我觉的有必要在这里记录下,避免以后再折腾。这事得从昨日做程序时间和内存优化说起。
1.时间优化
程序是.NET宿主+BOLT界面组合成的,刚开始我按部就班地先优化程序启动部分的加载时间。基本上是针对程序启动阶段的每个模块的加载做时间分析,按耗时大小顺序进行优化。这部分的优化技术主要包括:
移除无用的加载代码。
延迟加载未直接使用到的模块。
并发加载无空间和时间耦合的模块。
合并和提取重复执行的相同代码。
减少耗时的通信。
尽量减少Dictionary,这是由于string的Hash值计算并不快,数据量大查找慢。

2.空间优化
紧接着第二部分是做空间优化,一开始我只是随意做点能肉眼能看得出来的数据内存优化,比如
- 查找是否有耗时string拼接,改为StringBuilder。这个倒是由于习惯良好,并没有发现。
- 查找无用数据,并去除。
- 查找冗余的数据,合并之。
- 查找大量临时变量产生的地方,添加垃圾收集。
- 用.NET Memory Profile做分析,找出可以优化的内存优化掉。
- 网上搜了下各种内存优化技术,感觉靠谱的做一下,感觉好奇的做下实验。
- 通过二分搜索,找到内存变化比较大的地方进行代码行定位

这个过程并不总是按顺序进行的,交叉进行,然后有一处BOLT调用C#的委托被我注释掉后,神奇的事情发生了,物理内存一下子从100多M减少到30多M,如果加上这句代码内存又会是100多M。然后,我就喊jennal一起来看这句神奇的代码,两个人一起做了半个多小时的测试,最后发现只要在这个调用的函数里循环产生大量临时对象就会导致物理内存是100多M,否则就是30多M。中间我们去吃了个饭,回来继续折腾到10点多没搞定,我就先放弃了,结果坐公交车回来还睡过头,往回走了两站。
今天早上我就早起了去公司继续做单元测试。起初我以为是P/Invoke的问题,经过测试发现不是;接着我怀疑BOLT调用C#导致的问题,做了几组测试后发现只要在这个函数里做GC.Collect()就会导致问题的出现。我就拿这个现象找BOLT群里的几位群主咨询,结果自然是交流了很多意见还是没能解决这个诡异的问题。时间一直在流逝....

等快下班的时候,我发现我在程序启动的地方有调用了一句代码:SetProcessWorkingSetSize(),我把它注释掉后物理内存就是100多M,并不会减少到30多M。这句话这么神奇?马上Google了下,结果发现我被坑了,这句代码压根就是坑爹的。
使用这个函数来设置应用程序最小和最大的运行空间,只会保留需要的物理内存.当应用程序被闲置或系统物理内存太低时,操作系统会自动调用这个机制来设置应用程序的物理内存.
这句代码只是把程序的物理内存里没有用到的部分移到虚拟内存,如果对应的内存再次被用到时,会重新从虚拟内存加载到物理内存。这非但不能减少内存,反而增加了内存页错误,降低了程序的性能!我认真想了下,应该是我看到某个博客上的这句话时加过来做实验的,但忘记了删除。结果,就这样浪费了我一天时间。

二、高效
虽然我被坑爹了,但晚上跟BOLT的聪哥聊天,再结合用bolt开发过程的经验,突然我把很多东西串起来了。首先还是从上述折腾说起,这次折腾的过程中我理解到做内存优化必须老老实实地去做减少不必要资源的创建,增加资源的重用(内存池,对象池,连接池,线程池,享元模式【内蕴状态-外蕴状态】,共享对象...),及时释放可释放的资源(智能指针,IDispose,...),延迟加载。简单说就说:减少创建,增加重用,及时释放,延迟加载。具体在语言层面,又可以针对不同的场景优化数据结构的使用,如果是代码过程中的临时变量则应该对代码做调整减少不必要对象的创建等等。
SetProcessWorkingSetSize这个函数会引起内存页错误,当我跟聪哥说出我感觉内存优化的核心是:减少创建,增加重用,及时释放,延迟加载时,聪哥提到OS本身也是大量使用这样的技术,比如物理内存页,虚拟内存页本身就是按需加载,增加命中率(重用)的典型。我一下子就想起来我做的几个支持大数据的BOLT按需加载控件,不也是这样做的么?

我那几个控件都是带滚动条的支持大数据的控件,我归纳的核心做法是(以列表控件为例子):
- 设计两层结构的UI组织模式:ListControl,ListItem
- 设计ListItem的数据模型(ViewModel)比如叫ListItemData,用数组或者其他容器存储,根据不同场景可能有多层次结构。
- 为ListControl设计数据模型ADT:AddListItemData,RemoveListItemData,InsertListItemData,Clear
- 为ListControl设计UI刷新模型ADT:BeginUpdate、EndUpdate

使用方法是如下的Begin、End模式:
BeginUpdate()
   for ....
     AddListItemData()
   end
EndUpdate()

这里的核心原则是:
- ListControl最多只创建可视区域可以容纳的下的ListItem,一开始全部不可见。
- 在BeginUpdate的时候设置一个变量,挂起UI更新
- 在BeginUpdate和EndUpdate之间的代码负责更新ListControl的ViewModel。
- 整个ListControl只在EndUpdate这个函数里更新界面,包括重新绑定可视区域ListItem当前所绑定的ListItemData,设置可见性等等。
- 根据上一条,需要维护一堆ListItem和ListItemData之间的映射
- 所有交互操作,都会导致当前可视区域的ListItem所绑定的ListItemData的映射表的变动,所以全部交互都会采用上面的BeginUpdate和EndUpdate更新界面。

好了,这就是做按需加载支持大数据的控件的核心内容了。但这与操作系统内存有什么关系呢?也许你已经发现了,物理内存和虚拟内存之间的映射关系和ListItem与ListItemData之间的映射关系几乎一样!CPU执行代码就像是我们在操作界面,CPU执行代码时,指令流发现变化,当发现物理内存不存在时就去从虚拟内存按需加载。所以我们可以把物理内存看成是CPU的可见UI,CPU执行代码看成是UI交互,内存换页看成是动态绑定UI数据。这样我就成功地把带滚动条交互的UI模型用在了理解CPU,内存换页上了。然后,所有你在滚动条上做刷新优化的东西都可以用来理解OS,所有OS的优化技术应该也都可以用来优化滚动条控件。今天就写到这里,下次有时间再写其他的东西。