March comes in like a lion, and goes out like a lamb


国庆长假时突感无聊就找了一个补番推荐,然后就打开了这部番,谁知一看就不可收拾,一口气就看完了两季44集,以及很久没有这种感觉了,每一集都看的很舒服而且停不下来,上一次这么完整的看一部番剧应该是看钢炼的时候了吧.回到这部番,虽然讲的是主角下将棋的故事,一开始我是这么认为的,但是随着故事的发展才发现,将棋只是一部分,最重要的还是主角的成长和其他人的故事.

可以说零是不幸的,因为他自幼失去双亲和妹妹,虽然被父亲的朋友收养但是在新家里不受”姐姐”和”弟弟”的尊敬,最终害怕养父和子女的关系继续恶化而自己搬出来住,在学校中也因为研究将棋而没有同龄玩伴而被孤立.但是也可以说零是幸运的,而且比大多数人都幸运,因为有川本这样一个耀眼的”家”和川本三姐妹,二海堂这样的好友和林田这样关心学生的班主任.但这也就是人生吧,苦难和快乐总是交织的,而只有经历了深沉的苦难的人,才能更好的把握那仅有的幸福.

虽然很多人都说这部番节奏太慢了,但是我觉得就这样,就很好,像流水一样慢慢地慢慢地将每一个人的故事都讲了出来,这也是我觉得这部番好的一个原因,每一个人物都塑造的很成功,有为了不辜负乡亲们的期待而慢慢变强的岛田师兄,有因为身体原因而想在将棋上和别人公平对决的二海堂,有被挚友们托付了将棋愿望而66岁还坚守在A级的柳原棋匠,有二十多年容颜不变但患了听觉障碍的棋鬼宗谷名人,等等等等…每一个故事,每一个人都在为了生活而奋斗,这样不是就很好吗

这部番让我感到喜欢的还有画风,虽然一开始有点接受不能,但是在习惯之后,这种鲜明的用画面来表达情绪的画风反而变得很好,并且作画很跳跃,到最后甚至很喜欢这种画风,快乐时一切都是花样的,悲伤时又是如此的萧条和黑暗,而那无时不在的写成文字并讲出来的声音,也令人印象深刻.

零从一开始的”没有拼搏的理由”,到最后开始全力以赴,生活也从孤零零的阴暗的房间变成了多彩的世界,生活不易,每个人都在用自己的方式和生活斗争着,就算是零,从小就独自一人,在故事的最后,身边也有这么多人在相互扶持,我觉得这就是这部番给我最大的温暖吧.

给我印象很深的是在chapter 21中零的独白:

难道都是我的错吗?!
那么你告诉我该怎么办啊?!
开什么玩笑!
你弱你有理吗?!
因为弱才会输啊!
拜托你去学习啊!
我知道你在偷懒啊!
不要说什么,道理我懂,但我做不到,之类的话啊!
既然这样,那干脆别来啊!
我可是赌上了全部啊!
除了将棋,我一无所有啊!
不要借酒来逃避现实啊!
我对弱者没兴趣啊!

是啊,这个故事不仅是讲给零听的啊,而当零被岛田拍醒的那一刹那,我知道也知道了:

虽然嘴上说没有战斗的理由,但我知道自己体内住着一头野兽,就算把周围东西咬碎也要只为生存而奔走的野兽
不论让谁变得不幸,无论世界变成什么样子

可能也就是三月的狮子这个名字的由来吧:青春如同狮子一般咆哮着降临,最也难免以柔和的方式度过余生.

这个故事是讲的如此之好,没有煽情也没有夸大,而是把故事摆在你面前,无论是快乐还是伤心,无论是光明还是黑暗,都是如此的直击人性,令人难以忘怀,而在这个故事仍没有结束的夜晚,在我心中也流淌着一丝的温暖.

最后放一首ED,是米津玄師的orion.

あなたの指が その胸が その瞳が
眩しくて少し 眩暈がする夜もある
それは不意に落ちてきて
あまりにも暖かくて
飲み込んだ七色の星
弾ける火花みたいに
ぎゅっと僕を困らせた
それでまだ歩いてゆけること
教わったんだ
神様どうかどうか
声を聞かせて
ほんのちょっとでいいから
もう二度と離れないように
あなたと二人あの星座のように
結んで欲しくて
夢の中でさえどうも
上手じゃない心具合
気にしないでって嘆いたこと
泣いていたこと
解れた袖の糸を引っぱって
ふっと星座を作ってみたんだ
お互いの指を星として
それは酷くでたらめで
僕ら笑いあえたんだ
そこにあなたがいてくれたなら
それでいいんだ
今ならどんなどんな
困難でさえも
愛して見せられるのに
あんまりに柔くも澄んだ
夜明けの間ただ眼を見ていた
淡い色の瞳だ
真白でいる陶器みたいな
声をしていた冬の匂いだ
心の中静かに荒む
嵐を飼う闇の途中で
落ちてきたんだ僕の頭上に
煌めく星泣きそうなくらいに
触れていたんだ
神様どうかどうか
声を聞かせて
ほんのちょっとでいいから
もう二度と離れないように
あなたと二人この星座のように
結んで欲しくて
結んで欲しくて

你的手指 你的胸口 还有你的眼眸
如此的闪耀 在这夜里让人眩晕
不经意间落下的
如此温暖的
将我吞噬的七色之星
宛如四射的火花
让我无比困扰
不过我也因此受益匪浅
明白了我还能继续前进
神啊 怎么办啊 怎么办啊
让我听到你的声音
哪怕只有一瞬也没关系
为了让我们再也不分开
希望你我二人 就像那互相连接的星座
永生相随
就算是在梦中的世界
好像还是会屡屡受挫
叹着气说不要在意
眼泪也落了下来
将袖口脱落的线
试着做成一个星座
将你我的指尖当做星星
这样太过离谱
我们看着对方笑了起来
其实只要你一直在那里的话
我就很心满意足了
现在
不论有多大的困难在眼前
为了你我都会甘之如饴
可这黎明实在是 过于柔和澄澈
所以我只能 一直望着你的眼眸
那双有着淡淡色彩的眼眸
犹如陶器一般 纯白无暇
就像围绕在耳边的 冬天的气息
在心中 静静肆虐的暴风雨
在那漆黑一片的途中
降临在我的头顶
无数明暗闪烁的星 如今就近在指尖
让我有想哭的冲动
神啊 怎么办啊 怎么办啊
让我听到你的声音
哪怕只有一瞬也没关系
为了让我们 再也不分开
希望你我两人 就像那互相连接的星座
永生相随
永生相随


Continuations 对于程序设计的意义,就像达芬奇密码对人类历史的意义:即对人类最大秘密的惊人揭示。也许不是,但他在概念上的突破性至少和负数平方根的意义等同


看到CPS变换却不是在SICP学习的时候,而是在国庆前一天翻看王垠的GTF-Great Teacher Friedman时,当看到:

一个例子就是课程进入到没几个星期的时候,我们开始写解释器来执行简单的 Scheme 程序。然后我们把这个解释器进行 CPS 变换,引入全局变量作为”寄存器” (register),把 CPS 产生的 continuation 转换成数据结构(也就是堆栈)。最后我们得到的是一个抽象机 (abstract machine),而这在本质上相当于一个真实机器里的中央处理器(CPU)或者虚拟机(比如 JVM)。所以我们其实从无到有,“发明”了 CPU!从这里,我才真正的理解到寄存器,堆栈等的本质,以及我们为什么需要它们。我才真正的明白了,冯诺依曼体系构架为什么要设计成这个样子。

的时候,被兴趣吸引然后顺手搜索了一下CPS是个什么东西,结果便在这之上消磨了2天的时光,也是做一下总结罢.

What:什么是CPS?

在讨论CPS变换的时候我们在讨论什么,那就要从CPS开始讲起,既然(CPS)被称为一种style,那么可以认为它就是一种编码风格,而CPS变换也就是把我们的代码格式化成_Continuation-passing style_的过程.下面是一个关于CPS的解释:

CPS,是Continuation Passing Style的缩写,它是一种编码风格,函数执行完以后,并不通过返回值,而是调用它自己的Continuation来完成计算。

How:怎么做CPS(变换)

知道了定义之后,就可以开始做CPS变换了,虽然定义很抽象但是在看了几个例子之后还是可以理解这种书写代码的风格的.

下面是一个简单的例子(并不对特定的语言讨论,可以看成是伪代码):

1
2
3
4
5
6
7
// 一般的写法
func add(x, y) = x + y
print add(a,b)

// CPS风格的写法
func add(x, y, fun) = fun(x+y)
add(a, b, print)

对上面代码的解释:在一般的写法中我们定义了一个相加函数然后向控制台打印了a+b的结果.而CPS风格的写法是将一个print函数传递给函数add,在执行完add之后将结果传递给打印函数然后输出.也可以理解为add函数做完了自己的活之后将结果和控制权交到了print手里然后print输出了结果并返回了,但是print也可以继续传递这个结果给别人.

再举一个阶乘的例子:

1
2
3
4
5
// 一般写法
func f(x) = x == 1 ? 1 : x * f(x-1)f(4)

// CPS风格写法
func f(x, k) = x == 1 ? k(1) : f(x-1, lamdba(v):k(v*x))f(4, lamdba(v):v)

因为CPS约定:每个函数都需要有一个参数kont,kont是continuation的简写,表示对计算结果的后续处理,而对kont的约定是:kont为一个单参数输入,单参数输出的函数(假定print符合要求).

Why:为什么需要CPS变换

在例子中可以看到CPS的主要的功能就是:在一个函数执行结束之后,将返回值交给下一个函数,但是到现在为止,有一个很明显的问题是显而易见的,那就是我们可能会写出一个很长的函数调用串.也就是说这个看上去只是花哨了一点的代码风格在没有优势的情况下还带来了缺点,所以下面就整理一下我在浏览时看到的CPS的使用和优势(这个缺点其实不致命因为可以通过语法糖来解决).

执行顺序

来考虑这样一个问题,现在我们有2行代码:

1
2
print "enter something"
getInput()

很简单,在终端产生一个提示并且获取用户的下一个输入,但是问题是:我们不能保证代码执行的顺序,它们执行的顺序完全取决于编译器怎么安排.那么让我们来用CPS重写一下:
print ("enter something", getInput())

结果是很明显的,我们可以用这种方法来强制决定函数的执行顺序,这一点在函数式编程中是很重要的.当然在并发中,这也可能会有点用(没深究).

堆栈

来考虑一下阶乘函数的调用链:

1
2
3
4
5
6
7
8
9
// 一般写法
f(4) ==> 4*f(3) ==> 4*3*f(2) ==> 4*3*2*f(1) ==> 4*3*2*1

// CPS风格写法
f(4) = f(3, lamdba(v):(v*4))
= f(2, lamdba(v):(lamdba(v):(v*4)(v*3)))
= ...

改写之后可以化简为:v = 1 ==> 2*v ==> 3*v ==> 4*v ==> v

可以看到,第一种递归就是经典的递归模型,每次递归调用自己的时候都需要将当前的函数状态入栈然后进入到下一个函数中直到到递归终止情况然后逐一向上返回结果最后得出答案.但是在第二种CPS风格中,我们可以看到状态是顺序的,计算是从1开始向后乘到4然后返回,这是因为k承载了每次计算的结果并向后传递,也就是说我们不需要保存函数当前的状态而可以直接调用下一个函数,也就是说CPS变换将普通递归函数变成了尾递归.可以这么理解:递归中的保存状态的栈和kont函数是等价的,并且kont函数还有一个优势:因为kont函数可以被显示调用,也就是说我们可以在任意时刻任意情况下,调用函数的某一时刻的状态,只要它是CPS风格的!!

下面给出一段博客的引用,可能解释的更清楚:

这样我们就知道了什么是“当前 continuation”。它有什么意义?一旦我们得到了当前的 continuation 并将它保存在某处,我们就最终将程序当前的状态保存了下来——及时地冷冻下来。这就像操作系统进入休眠状态。一个 continuation 对象里保存了从我们获得它的地方重新启动程序的必要信息。操作系统在每次发生线程间的上下文切换时也是如此。唯一的区别是它保留着全部控制。请求一个 continuation 对象(在 Scheme 里,可以调用 call-with-current-continuation 函数)后,你就会获得一个包括了当前 continuation 的对象,也就是堆栈信息(在 CPS 程序里就是下一个要调用的函数),可以把这个对象保存在一个变量(或者是磁盘)里。当你用这个 continuation “重启”程序时,就会转回到你取得这个对象的那个状态,这就象切换回一个被挂起的线程或唤醒休眠的操作系统,区别是用 continuation,你可以多次地重复这一过程,而当操作系统被唤醒时,休眠信息就被销毁了,如果那些信息没有被销毁,你也就可以一次次地将它唤醒到同一点,就象重返过去一样。有了 continuation 你就有了这个控制力!

应用:消除JS回调地狱

虽然还没有完整的学过JS和前端的技术,但是在学Django的过程中接触到的一些简短的JS也觉得这门语言”似乎”有很多的缺陷(个人感觉,主要是感觉各种{({()})}嵌套确实不够优雅),但是JS又确实是一门经常被拿来讨论一些FP问题的语言(可能因为JS支持函数作为first-class还能在浏览器直接运行的原因吧).在看CPS的过程中也看到了关于JS回调地狱的问题.

回调地狱

先来丢一段js代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
fs.readdir(source, function (err, files) {  
if (err) {
console.log('Error finding files: ' + err)
} else {
files.forEach(function (filename, fileIndex) {
console.log(filename)
gm(source + filename).size(function (err, values) {
if (err) {
console.log('Error identifying file size: ' + err)
} else {
console.log(filename + ' : ' + values)
aspect = (values.width / values.height)
widths.forEach(function (width, widthIndex) {
height = Math.round(width / aspect)
console.log('resizing ' + filename + 'to ' + height + 'x' + height)
this.resize(width, height).write(dest + 'w' + width + '_' + filename, function(err) {
if (err) console.log('Error writing file: ' + err)
})
}.bind(this))
}
})
})
}
})

解决方案

当然这一堆括号和大括号看着很让人抓狂而且也很难马上掌握执行的顺序,当然这里不会来优化这段代码(我不会,哈哈),来看一个简单的例子:

1
2
3
4
5
6
7
8
9
// 第一次ajax,查询出id
$.get('http://xxx/user?name=jxy', function(data){
var id = data.id;
// 第二次ajax,根据id查询出需要的数据
$.get('http://xxx/another?id='+id, function(data){
// 这里才是真正的处理逻辑
// do something...
});
});

用async/await优化之后可以变成(据说是终极解决方案):

1
2
3
4
5
6
7
8
9
10
11
// 伪代码
// 用async修饰一个函数
async function getData(){
// 用await标记异步操作,会自动等待异步操作执行完毕之后再继续向下执行
const user = await $.get('http://xxx/user?name=jxy');
const id = user.id;
const data = await $.get('http://xxx/another?id='+id);
// 真正处理data
// do something...
};
getData();

关于async/await或者说协程,讨论起来又是无止境的了,但是基本的思想和CPS很吻合,也就是我们需要在切换上下文的时候如何来保存状态和恢复之前的状态.而js的async/await其实也只是function*+yield的一个语法糖而已,其核心就是不断的将 yield 的 next 值追加到 promise 链中,达到“一步接一步”执行的效果.

在看资料的时候还看到github上有一个Continuation.js项目,是直接使用CPS来解决回调地狱问题的,给出的例子如下:

原始js代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function textProcessing(callback) {  
fs.readFile('somefile.txt', 'utf-8', function (err, contents) {
if (err) return callback(err);
//process contents
contents = contents.toUpperCase();
fs.readFile('somefile2.txt', 'utf-8', function (err, contents2) {
if (err) return callback(err);
contents += contents2;
fs.writeFile('somefile_concat_uppercase.txt', contents, function (err) {
if (err) return callback(err);
callback(null, contents);
});
});
})
;}
textProcessing(function (err, contents) {
if (err)
console.error(err);
});

使用Continuation.js之后:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function textProcessing(ret) {  
fs.readFile('somefile.txt', 'utf-8', cont(err, contents));
if (err) return ret(err);
contents = contents.toUpperCase();
fs.readFile('somefile2.txt', 'utf-8',
cont(err, contents2));
if (err) return ret(err);
contents += contents2;
fs.writeFile('somefile_concat_uppercase.txt', contents, cont(err));
if (err) return ret(err);
ret(null, contents);
}
textProcessing(cont(err, contents));
if (err)
console.error(err);

至少,看起来确实优雅了很多.

应用:Web应用

试想这么一个场景:用户向服务器请求了一张表单比如是注册,然后用户在网页上进行了一系列的操作之后提交了这张表单,注册成功.

在开发的过程中,一般来说我们要写一个getForm的方法来响应请求表单和postFrom的方法来响应提交表单.但是如果我们用CPS来思考这个问题呢?就会变成下面这样:用户申请了一张表单,我们生成一个函数来处理它,然后将这个函数保存起来,等到用户提交的时候,我们再调用这个函数将参数给它就可以完成注册.

在这个场景中的优势就是,我们不需要分离get和post逻辑,因为这其实都是属于用户注册这个过程中的事,而且,因为continuation可以在任何时候被调用而且不止调用一次(只要被保存了),那么优势就很明显了,我们只要缓存continuation环境,就可以节省很多的服务器性能.

我相信在web上,这个理念迟早会流行,毕竟计算机中十年不变的东西就是各种语言正在发布的新特性(hhh).

应用:JavaFlow

这只是在Google的过程中搜到的一个算是副产物吧,但是也在这里记录一下,大致就是可以在Java里使用JavaFlow来实现continuation.
1
2
3
4
5
6
7
8
9
10
11
12
13
class MyRunnable implements Runnable {  
public void run() {
System.out.println("started!");
for( int i=0; i<10; i++ )
echo(i);
}
private void echo(int x) {
System.out.println(x);
Continuation.suspend();
}
}
Continuation c = Continuation.startWith(new MyRunnable());
System.out.println("returned a continuation");

然后就可以调用c了:
1
2
Continuation d = Continuation.continueWith(c);
System.out.println("returned another continuation");

自动CPS变换

之所以将其放在最后,是因为我也还没有时间将其搞懂,而且似乎对于理解CPS变换来说影响并不大,这里就先暂时用一张图来代替吧(王垠40行代码的注释版)

参考博客

GTF - Great Teacher Friedman

CPS变换与CPS变换编译

CPS的地位

基于CPS变换的尾递归转换算法

函数式编程与Continuation/CPS

用 continuation 开发复杂的 Web 应用程序

时间倏忽而逝

还有一篇文章先留个档,有时间再消化:
Representing Control—A Study of the CPS transformation

我因为写了一部人们把它和《禅与摩托车维修艺术》相比较的书而感到甚受恭维。我希望拙作(《时间简史》)和本书一样使人们觉得,他们不必自处于伟大的智慧及哲学的问题之外。——by霍金

在历经了多个早起的清晨和国庆回家路上的几个小时之后,也_勉强_算是走完了这一次的肖陶扩,这是一本看书名不知所云但是却发人深省的书,主要讲述了作者和儿子在一次穿越美国的长途摩托车旅行中发生的故事以及作者的思考和感受.在看到霍金对这本书的评价之后我才豁然:我们生活中处处充满了哲学,但是当我们听到哲学这个词的时候却会将其束之高阁,但是《禅》这本书却从旅行和摩托车修理的角度,将作者自己对哲学的思考和理解很好的讲述给了读者听,这是很了不起的.

骑摩托车旅游和其他的方式完全不同。坐在汽车里你只是被局限在一个小空间之内,因为已经习惯了,你意识不到从车窗向外看风景和看电视差不多。你只是个被动的观众,景物只能呆板地从窗外飞驰而过。 骑摩托车可就不同了。它没有什么车窗玻璃在面前阻挡你的视野,你会感到自己和大自然紧密地结合在了一起。你就处在景致之中,而不再是观众,你能感受到那种身临其境的震撼。脚下飞驰而过的是实实在在的水泥公路,和你走过的土地没有两样。它结结实实地躺在那儿,虽然因为车速快而显得模糊,但是你可以随时停车,及时感受它的存在,让那份踏实感深深印在你的脑海中。

我们不再是观众而是紧密地和作者的思想结合在了一起,深深的印在了我的脑海中.

在看这本书的过程中,我的脑海中就有一个词一直存在着:物我两忘,个人觉得这和作者想要表述的良质是比较接近的.虽然这里讲的禅和中国人对禅的定义是非常不一样的,但是从根本上来说,都是对于哲学的讨论.禅与摩托车可以对应于哲学中的物体和自我与世界的二元对立,而作者在书中着重讨论的也是古典和浪漫如何才能够有机的结合在一起,比如,在修理摩托车上.也就是说:如何将物体和自我,主观和客观统一起来,达到物我两忘的境界.

在讲到修理摩托车的时候:

在修理机器这方面,如果你的自我太强,往往无法把工作做好。因为你总是会被愚弄,很容易犯错,所以修理人员自大的个性对他颇为不利。如果你认识很多技术人员,我想你会同意我的观点,他们往往相当谦虚而且安静。当然,也有例外。不过即使他们起初无法保持安静和谦逊,长久工作下来,也会变成这样的个性。同时,他们还具有高度的警戒心,专注而又懂得怀疑,不会以自我为中心。

以我的理解,所谓的物我两忘或者修理摩托车的禅,就是要抛弃那一堆冰冷的说明书和零件分解,而将其视为一个整体,需要的是对于摩托车的整体的把握,在某个零件受损的时候,会冷静的从摩托车的角度来考虑这个问题,而不是发现一个零件坏了,就努力试图将其从摩托车上拆下来换一个新的上去,因为这样没有考虑到每一个零件和摩托车本体的联系,而只是将其视为一个零件,将问题视为一个任务去完成,很有可能这个零件的问题是由别的部位导致的,也有可能在你只想要拆除它的时候,因为你的目光只停留在这个零件上而造成了别的问题.修理一辆摩托车,需要的是掌握它的全部,不仅仅是所有的技术细节和零件,还有对车的_感性的或者浪漫的认识_,或者说,我们对这辆车的一生的每一个状态都很了解.

一旦真正地投入了工作之中,就可以说是在关心自己的工作,这就是关心的真正意义——对自己手中的工作产生了认同感。当一个人产生这种认同的时候,他就会看到关系的另一面——良质。

这是这本书中最能发我深思的地方了,而这本书作为计算机的必读书之一,我觉得最有意义的也就在于此,虽然科技的发展不仅仅只在计算机上,但是CS确实成为了现在科技发展最瞩目的地方之一,而作为一个CS的学生,在开始的阶段就读到这样的书,是我的幸运,它指引我去寻找理解世界的方式和获得内心平静的方法,而作为一个程序员,更不应只停留在技术细节和代码的世界中,我们需要的是浪漫抑或者说是编写软件的禅,它也让我理解了,什么是的,也就是,什么是良质的,我们应该如何去做技术,如何让做出的东西中有良质.

几分钟之后,我们顺着这条路骑到了山顶,然后又笔直地往山谷落下。一路风景十分优美。我觉得这个山谷和美国其他的山谷完全不同。往南边一点就是所有葡萄美酒的产地。山坡像波浪一样起伏,呈现出优美的曲线,而路也是蜿蜒曲折。我们的身体和车子缓缓地顺着山路向下走,同时向路边倾斜过去,几乎可以碰到树叶和树枝。高山地区的岩石和枞树远远落在身后,在我们周围是平缓的山坡和葡萄树,还有许多紫色和红色的花朵。从山谷冒出了浓郁的雾气,那是森林的气息和花香融合在了一起。在遥远的那一端,则是看不到但可以微微嗅得到的海洋气息。
…… 人只要活着就会发生不愉快的事和不幸的事。但是我现在有一种以前没有过的感觉,这种感觉并不只停留在表面,而是深入内里:我们赢了。情况正在慢慢好起来。我们几乎可以这样期待。

是的,情况正在慢慢好起来,我觉得一本书的价值不仅在于作者倾授了多少他的观点给你,而是在于阅读完这本书之后,能不能在你的脑海中留下作者思考的痕迹或者说潜意识,能够在遇到别的情况的时候,用别人(作者)的方式去思考问题从而获得答案,如果说读完一本书之后能够对其_久久不能忘怀_的话,那么这本书的价值就超出了其文字所能承载的了.

番外

在看到王垠的什么是“对用户友好”的时候,我觉得对用户友好这一点就像是需要结合理性的和感性的认识的一个例子,就在这里记录一下,可能这就是做一个好软件的禅吧.

爱因斯坦说:“Any intelligent fool can make things bigger and more complex… It takes a touch of genius - and a lot of courage to move in the opposite direction.”

确实,大师做的事情是都是简单的,道不远人,也就是霍金说的:

不必自处于伟大的智慧及哲学的问题之外

此言得之,而我们也不应该身处一个好的软件之外,不应该在使用它的时候还需要看一大堆的说明书和教程,好的软件,应该是傻瓜式的,也就是KISS原则.

很多程序员都会注意到这些机器界面的抽象,让使用者尽量少的接触到实现细节。可是他们却往往忽视了人和机器之间的界面。也许他们没有忽视它,但是他们却用非常不一样的设计思想来考虑这个问题。他们没有真正把人当成这个系统的一部分,没有像对待其它机器模块一样,提供具有良好抽象的界面给人。

一个良好的界面不应该是这样的。它给予用户的界面,应该只有一些简单的设定。用户应该用同样的方法来设置所有程序的所有参数,因为它们只不过是一个从变量到值的映射(map)。至于系统要在什么地方存储这些设定,如何找到它们,具体的格式,用户根本不应该知道。

所以我们看到,“对用户不友好”的背后,其实是程序设计的不合理使得它们缺少抽象,而不是用户的问题。

也就是说,虽然开发者每天都在讲抽象,但是却没有将用户也抽象进整个的系统中,或者说将用户和整个系统剥离了,使得用户所接触到的是冷冰冰的操作手册和一堆配置信息,而《禅》中告诉我们,操作手册是没用的,或者说写操作手册给用户看本身就不是一个的行为.

还记得邹欣在《构建之法》中讲的一个例子:微软会征集用户来测试office软件,而让程序员在单向玻璃后面看用户的操作,当用户对某一个操作很疑惑或者找不到某个功能的位置的时候,程序员在后面都会很着急,因为这个功能似乎就在那里,但是用户就是没有去点击它.当然测试结束之后团队是很羞愧的,因为自己为很好的用户交互逻辑,到了用户那里变成了反逻辑或者很难的操作.

这其实都从不同的角度在思考软件和人的关系,在编写软件的过程中程序员知道代码需要抽象,也都知道抽象的好处,但是在和用户的交互这一层上,这样的抽象却被剥离了,缺失了,不合理的菜单布局和操作逻辑比比皆是.

对于开发者来说,如何做出对用户友好的软件就是需要开发软件的禅的地方了.

0%