If you'd like to automatically receive updates to this feed, you can subscribe now.
这是个StackOverflow上的问题 How can you program if you’re blind? 。在看到这个问题的时候,我感到应该不可能,但是我错了,这个问题的前两个答案让我深深地震憾了。
第一个答案的回复人是Jared(其在StackOverflow上的积分有将近14K),但是你能想得到他是一个盲人吗?他回复到——
我是一个完全失明的大学学生,我做过一些程序员的实习工作,所以我的回复基于我的这些经历。我使用Windows XP 和 Jaws 来为了读出屏幕上的内容。
对于Java 编程,我使用eclipse这个强大的IDE。我使用SWT开发GUI。对于.NET编程,其使用Visual Studio 2005,使用Jaws可以非常容易地操作VS2005,而且其还有一些很不错的脚本来可容易地用来做表单设计。
对于C/C++,我使用cygwin + gcc 也使用emacs 和 vim 做出编辑器(使用Emacspeak虽然有时候有点迟钝)。在实习过程中,我做了很多和Z/OS相关的编程工作。我使用rlogin通过cygwin登录大型机的USS系统,并使用C3270作为其3270仿真器来访问大型机的ISPF部分。
我依赖于合成语音系统,也需要 Braille display, 我发现使用合成语音系统很快,但是使用 Braille display有时候有些问题。比如程序有太多的嵌套括号。
关于Braille display,又叫盲文显示机,是能以盲文进行输出的电子机械式设备。一般来说,该设备通过在平坦表面上打孔来实现点阵的表现。有了该设备的帮助,无法使用一般的显示设备的失明用户也能够阅读文字。如下所示。
第二个答案是Saqib提供的,其个人主页是http://www.saqibshaikh.com/,目前在Microsoft的Bing项目组,他回答到:
我是盲人,我对Windows, Mac, Linux 和 DOS有13年的编程经验了,我会的编程语言是C/C++, Python, Java, C#或是其它相似的语言,虽然问题问的是怎么来设置盲人的环境,但是我想从盲人怎么使用电脑来回答。
有些人使用“语音环境”,如T. V. Raman程序员和Emacspeak 环境。这样的环境需要有读屏程序来监控操作系统的行为,并通过合成语音系统或是Braille display 来告诉盲人屏幕上有什么。这样一样,盲人就可以操作任何的应用程序了。
我个人这段时候使用Visual Studio 2008(注:作者是09年回的这个贴的),用其来做一些修改。我关闭了一些VS2008的功能,如显示错误,因为这会让我分心。在加入微软以前,我都是在用notepad这样的东西开发程序。
对于读屏软件,我需要设置一下,以便其告诉我缩进。老实说我不太关心这个事,因为VS2008对程序缩进做得很好。但是对于Python来说,这个功能相当重要。最终,Emacspeak 可以使用不同的声音来让我区分缩进的语句块,以及一些语法(关键词,注释,标识,等等。)
对于Saqib,大家有兴趣可以看看他的视频访谈:Saqib Shaikh and Scott Hanselman: Designing for Accessibility
这个问题中多次提到了Google的盲人程序员 T.V. Raman,我在网上搜了一下他,他前段时间来过北京,新京报在今年早期报道过他——《T.V 拉蒙,互联网界也有“盲剑客” ——Google盲人工程师讲述软件设计之路》
在这篇报道中,他经历过IBM, Adobe和Google 这三个公司,他可以在23秒内复原盲人魔方,1989年他就得到一台给盲人用的语音合成器和当时最先进的读屏软件。他现在使用电脑 没有任何障碍,他天天都上网浏览信息,他还可以使用特别的手机来看地图。
不知道你看完这些人的经历后,你有什么感觉?
下面是近期收录的一些文章和资源,希望对你有用。
Mozilla's Gecko rendering engine main flow
—— 更新 2011.9.20 21:00 ——
@xzhaoyang 在留言中问我有没有C写CGI的文章,我看过最好的一篇是下面这篇:
http://www.tutorialspoint.com/cplusplus/cpp_web_programming.htm (注意fanqiang)
前几天酷壳发布过“vim简明攻略”,不知道大家练得怎么样了。如果你练了一下,那么这里这个速查卡就会对你有帮助了。以前本站也有过一个(vim速查卡),不过其太简单了。我觉得这个很不错,很全,很直观。这个速查卡来自这里。其用颜色标注了级别:
下面的图片点击可以看大图:
你还可以下载PDF版的和Excel版的,如果你是色盲的话,还有蓝色版PDF的。如果你不是很喜欢的话,这里还有几个:
我们有很多Coding Style 或 代码规范。但这一条可能会经常被我们所遗忘,就是我们经常会在函数的参数里使用bool参数,这会大大地降低代码的可读性。不信?我们先来看看下面的代码。
当你读到下面的代码,你会觉得这个代码是什么意思?
widget->repaint(false);
是不要repaint吗?还是别的什么意思?看了文档后,我们才知道这个参数是immediate, 也就是说,false代表不立即重画,true代码立即重画。
Windows API中也有这样一个函数:InvalidateRect,当你看到下面的代码,你会觉得是什么意思?
InvalidateRect(hwnd, lpRect, false);
我们先不说InvalidateRect这个函数名取得有多糟糕,我们先说一下那个false参数?invalidate意为“让XXX无效”,false是什么意思?双重否定?是肯定的意思?如果你看到这样的代码,你会相当的费解的。于是,你要去看一下文档,或是InvalidateRect的函数定义,你会看到那个参数是 BOOL bErase,意思是,是否要重画背景。
这样的事情有很多,再看下面的代码,想把str中的”%USER%”替换成真实的用户名:
str.replace("%USER%", user, false); // Qt 3
TNND,那个false是什么意思?不替换吗?还是别的什么意思,看了文档才知道,false代码大小写不敏感的替换。
其实,如果你使用枚举变量/常量,而不是bool变量,你会让你的代码更易读,如:
widget->repaint(PAINT::immediate); widget->repaint(PAINT::deffer); InvalidateRect(hwnd, lpRect, !RepantBackground); str.replace("%USER%", user, Qt::CaseInsensitive); // Qt 4
如果对这个事不以为然的话,我们再来看一些别的示例,你不妨猜猜看看下面的代码:
component.setCentered(true, false);
这什么玩意儿啊?看了文档你才知道,这原来是 setCentered(centered, autoUpdate);
new Textbox(300, 100, false, true);
这又是什么啊?看了文档才知道,这是创建一个文本框,第三个参数是是否要滚动条,第四个是是否要自动换行。TNND。
上面的情况还不算最差,看看下面的双重否定。
component.setDisabled(false); filter.setCaseInsensitive(false)
再来一个,如果你读到下面的代码,相信你会和我一样,要么石化了,要么凌乱了。
event.initKeyEvent("keypress", true, true, null, null, false, false, false, false, 9, 0);
看完这篇文章,我希望你再也不要把bool为作为函数参数了。除非两个原因:
【更新2011/9/8】当然,别的参数也会有一样的问题,比如:new Textbox(300, 100, false, true);
中的300 和 100,不知道是坐标还是长宽,只不过,一般长度或坐标这样的参数都不会被hard code,都会有变量名,而bool这种参数经常性地被传成true 和 false。 bool参数表现得更为明显一些罢了。
所以,程序中不要出现magic number,true/false 也是一种 magic number。但是,我想告诉大家,从API设计的角度来说,你无法强制调用者用常量来取代true/false,定义成枚举类型是最好的选择。
最后,如果你想设计一个好的API,强烈推荐你读一下Nokia的Qt的《API Design Principles》,本文就是其中的“Boolean Trap”。
(全文完)
vim的学习曲线相当的大(参看各种文本编辑器的学习曲线),所以,如果你一开始看到的是一大堆VIM的命令分类,你一定会对这个编辑器失去兴趣的。下面的文章翻译自《Learn Vim Progressively》,我觉得这是给新手最好的VIM的升级教程了,没有列举所有的命令,只是列举了那些最有用的命令。非常不错。
——————————正文开始——————————
你想以最快的速度学习人类史上最好的文本编辑器VIM吗?你先得懂得如何在VIM幸存下来,然后一点一点地学习各种戏法。
Vim the Six Billion Dollar editor
Better, Stronger, Faster.
学习 vim 并且其会成为你最后一个使用的文本编辑器。没有比这个更好的文本编辑器了,非常地难学,但是却不可思议地好用。
我建议下面这四个步骤:
当你走完这篇文章,你会成为一个vim的 superstar。
在开始学习以前,我需要给你一些警告:
当你安装好一个编辑器后,你一定会想在其中输入点什么东西,然后看看这个编辑器是什么样子。但vim不是这样的,请按照下面的命令操作:
ESC
键。现在,你知道如何在 Insert 和 Normal 模式下切换了。下面是一些命令,可以让你在 Normal 模式下幸存下来:
i
→ Insert 模式,按ESC
回到 Normal 模式.x
→ 删当前光标所在的一个字符。:wq
→ 存盘 + 退出 (:w
存盘,:q
退出) (陈皓注::w 后可以跟文件名)dd
→ 删除当前行,并把删除的行存到剪贴板里p
→ 粘贴剪贴板推荐:
hjkl
(强例推荐使用其移动光标,但不必需) →你也可以使用光标键 (←↓↑→). 注:j
就像下箭头。:help <command>
→ 显示相关命令的帮助。你也可以就输入:help
而不跟命令。(陈皓注:退出帮助需要输入:q)
你能在vim幸存下来只需要上述的那5个命令,你就可以编辑文本了,你一定要把这些命令练成一种下意识的状态。于是你就可以开始进阶到第二级了。
当是,在你进入第二级时,需要再说一下 Normal 模式。在一般的编辑器下,当你需要copy一段文字的时候,你需要使用 Ctrl
键,比如:Ctrl-C
。也就是说,Ctrl键就好像功能键一样,当你按下了功能键Ctrl后,C就不在是C了,而且就是一个命令或是一个快键键了,在VIM的Normal模式下,所有的键就是功能键了。这个你需要知道。
标记:
Ctrl-λ
我会写成 <C-λ>
.:
开始的命令你需要输入 <enter>
回车,例如 — 如果我写成 :q
也就是说你要输入 :q<enter>
.上面的那些命令只能让你存活下来,现在是时候学习一些更多的命令了,下面是我的建议:(陈皓注:所有的命令都需要在Normal模式下使用,如果你不知道现在在什么样的模式,你就狂按几次ESC键)
a
→ 在光标后插入o
→ 在当前行后插入一个新行O
→ 在当前行前插入一个新行cw
→ 替换光标所在位置的一个单词
0
→ 数字零,到行头^
→ 到本行第一个不是blank字符的位置(所谓blank字符就是空格,tab,换行,回车等)$
→ 到本行行尾g_
→ 到本行最后一个不是blank字符的位置。/pattern
→ 搜索pattern
的字符串(陈皓注:如果搜索出多个匹配,可按n键到下一个)
P
→ 粘贴yy
→ 拷贝当前行当行于ddP
u
→ undo<C-r>
→ redo
:e <path/to/file>
→ 打开一个文件:w
→ 存盘:saveas <path/to/file>
→ 另存为<path/to/file>
:x
,ZZ
或:wq
→ 保存并退出 (:x
表示仅在需要时保存,ZZ不需要输入冒号并回车):q!
→ 退出不保存:qa!
强行退出所有的正在编辑的文件,就算别的文件有更改。:bn
和:bp
→ 你可以同时打开很多文件,使用这两个命令来切换下一个或上一个文件。(陈皓注:我喜欢使用:n到下一个文件)
花点时间熟悉一下上面的命令,一旦你掌握他们了,你就几乎可以干其它编辑器都能干的事了。但是到现在为止,你还是觉得使用vim还是有点笨拙,不过没关系,你可以进阶到第三级了。
先恭喜你!你干的很不错。我们可以开始一些更为有趣的事了。在第三级,我们只谈那些和vi可以兼容的命令。
下面,让我们看一下vim是怎么重复自己的:
.
→ (小数点) 可以重复上一次的命令下面是一个示例,找开一个文件你可以试试下面的命令:
2dd
→ 删除2行3p
→ 粘贴文本3次100idesu [ESC]
→ 会写下 “desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu desu “.
→ 重复上一个命令—— 100 “desu “.3.
→ 重复 3 次 “desu” (注意:不是 300,你看,VIM多聪明啊).
你要让你的光标移动更有效率,你一定要了解下面的这些命令,千万别跳过。
G
→ 到第 N 行 (陈皓注:注意命令中的G是大写的,另我一般使用 : N 到第N行,如 :137 到第137行)gg
→ 到第一行。(陈皓注:相当于1G,或 :1)G
→ 到最后一行。
w
→ 到下一个单词的开头。e
→ 到下一个单词的结尾。> 如果你认为单词是由默认方式,那么就用小写的e和w。默认上来说,一个单词由字母,数字和下划线组成(陈皓注:程序变量)
> 如果你认为单词是由blank字符分隔符,那么你需要使用大写的E和W。(陈皓注:程序语句)
下面,让我来说说最强的光标移动:
%
: 匹配括号移动,包括(
,{
,[
. (陈皓注:你需要把光标先移到括号上)*
和#
: 匹配光标当前所在的单词,移动光标到下一个(或上一个)匹配单词(*是下一个,#是上一个)
相信我,上面这三个命令对程序员来说是相当强大的。
你一定要记住光标的移动,因为很多命令都可以和这些移动光标的命令连动。很多命令都可以如下来干:
<start position><command><end position>
例如 0y$
命令意味着:
0
→ 先到行头y
→ 从这里开始拷贝$
→ 拷贝到本行最后一个字符你可可以输入 ye
,从当前位置拷贝到本单词的最后一个字符。
你也可以输入 y2/foo
来拷贝2个 “foo” 之间的字符串。
还有很多时间并不一定你就一定要按y才会拷贝,下面的命令也会被拷贝:
d
(删除 )v
(可视化的选择)gU
(变大写)gu
(变小写)你只需要掌握前面的命令,你就可以很舒服的使用VIM了。但是,现在,我们向你介绍的是VIM杀手级的功能。下面这些功能是我只用vim的原因。
0
^
$
f
F
t
T
,
;
还有一个很有用的命令是 dt"
→ 删除所有的内容,直到遇到双引号—— "。
<action>a<object>
或 <action>i<object>
在visual 模式下,这些命令很强大,其命令格式为
<action>a<object>
和 <action>i<object>
d
(删除), y
(拷贝), v
(可以视模式选择)。w
一个单词, W
一个以空格为分隔的单词, s
一个句字, p
一个段落。也可以是一个特别的字符:"、
'、
)、
}、
]。
假设你有一个字符串 (map (+) ("foo"))
.而光标键在第一个 o
的位置。
vi"
→ 会选择foo
.va"
→ 会选择"foo"
.vi)
→ 会选择"foo"
.va)
→ 会选择("foo")
.v2i)
→ 会选择map (+) ("foo")
v2a)
→ 会选择(map (+) ("foo"))
<C-v>
块操作,典型的操作: 0 <C-v> <C-d> I-- [ESC]
^
→ 到行头<C-v>
→ 开始块操作<C-d>
→ 向下移动 (你也可以使用hjkl来移动光标,或是使用%,或是别的)I-- [ESC]
→ I是插入,插入“--
”,按ESC键来为每一行生效。在Windows下的vim,你需要使用 <C-q>
而不是 <C-v>
,<C-v>
是拷贝剪贴板。
<C-n>
和 <C-p>
在 Insert 模式下,你可以输入一个词的开头,然后按 <C-p>或是<C-n>,自动补齐功能就出现了……
qa
操作序列 q
, @a
, @@
qa
把你的操作记录在寄存器 a。
@a
会replay被录制的宏。@@
是一个快捷键用来replay最新录制的宏。示例
在一个只有一行且这一行只有“1”的文本中,键入如下命令:
qaYp<C-a>q
→
qa
开始录制Yp
复制行.<C-a>
增加1.q
停止录制.@a
→ 在1下面写下 2@@
→ 在2 正面写下3- 现在做
100@@
会创建新的100行,并把数据增加到 103.
v
,V
,<C-v>
前面,我们看到了 <C-v>
的示例 (在Windows下应该是<C-q>),我们可以使用 v
和 V
。一但被选好了,你可以做下面的事:
J
→ 把所有的行连接起来(变成一行)<
或 >
→ 左右缩进=
→ 自动给缩进 (陈皓注:这个功能相当强大,我太喜欢了)在所有被选择的行后加上点东西:
<C-v>
j
或 <C-d>
或是 /pattern
或是 %
等……)$
到行最后A
, 输入字符串,按 ESC。
:split
和 vsplit
.下面是主要的命令,你可以使用VIM的帮助 :help split
. 你可以参考本站以前的一篇文章VIM分屏。
:split
→ 创建分屏 (:vsplit
创建垂直分屏)<C-w><dir>
: dir就是方向,可以是hjkl
或是 ←↓↑→ 中的一个,其用来切换分屏。<C-w>_
(或<C-w>|
) : 最大化尺寸 (<C-w>| 垂直分屏)<C-w>+
(或<C-w>-
) : 增加尺寸
:help usr_02.txt
.!,
目录,寄存器,插件等很多其它的功能。学习vim就像学弹钢琴一样,一旦学会,受益无穷。
——————————正文结束——————————
对于vi/vim只是点评一点:这是一个你不需要使用鼠标,不需使用小键盘,只需要使用大键盘就可以完成很多复杂功能文本编辑的编辑器。不然,Visual Studio也不就会有vim的插件了。
(全文完)
本文来自“The most stupid C bug ever”,很有意思,分享给大家。我相信这样的bug,就算你是高手你也会犯的。你来看看作者犯的这个Bug吧。。
首先,作者想用一段程序来创建一个文件,如果有文件名的话,就创建真正的文件,如果没有的话,就调用?tmpfile()?创建临时文件。他这段程序就是HTTP下载的C程序。code==200就是HTTP的返回码。
else if (code == 200) { // Downloading whole file /* Write new file (plus allow reading once we finish) */ g = fname ? fopen(fname, "w+") : tmpfile(); }
但是这个程序,只能在Unix/Linux下工作,因为 Microsoft 的?tmpfile()的实现?居然选择了 C:\ 作为临时文件的存放目录,这对于那些没有管理员权限的人来说就出大问题了,在Windows 7下,就算你有管理员权限也会有问题。所以,上面的程序在Windows平台下需要用不同的方式来处理,不能直接使用Windows的tmpfile()函数。
于是作者就先把这个问题记下来,在注释中写下了FIXME:
else if (code == 200) { // Downloading whole file /* Write new file (plus allow reading once we finish) */ // FIXME Win32 native version fails here because // Microsoft's version of tmpfile() creates the file in C:\ g = fname ? fopen(fname, "w+") : tmpfile(); }
然后,作者觉得需要写一个跨平台的编译:
FILE * tmpfile ( void ) { #ifndef _WIN32 return tmpfile(); #else //code for Windows; #endif }
然后,作者觉得这样实现很不好,会发现名字冲突,因为这样一来这个函数太难看了。于是他重构了一下他的代码——写一个自己实现的tmpfile() – w32_tmpfile,然后,在Windows 下用宏定义来重命名这个函数为tmpfile()。(陈皓注:这种用法是比较标准的跨平台代码的写法)
#ifdef _WIN32 #define tmpfile w32_tmpfile #endif FILE * w32_tmpfile ( void ) { //code for Windows; }
搞定!编译程序,运行。靠!居然没有调用到我的w32_tmpfile(),什么问题?调试,单步跟踪,果然没有调用到!难道是问号表达式有问题?改成if – else 语句,好了!
if(NULL != fname) { g = fopen(fname, "w+"); } else { g = tmpfile(); }
问号表达式不应该有问题吧,难道我们的宏对问号表达式不起作用,这难道是编译器的预编译的一个bug?作者怀疑到。
现在我们把所有的代码连在一起看,并比较一下:
能正常工作的代码
#ifdef _WIN32 # define tmpfile w32_tmpfile #endif FILE * w32_tmpfile ( void ) { code for Windows; } else if (code == 200) { // Downloading whole file /* Write new file (plus allow reading once we finish) */ // FIXME Win32 native version fails here because // Microsoft's version of tmpfile() creates the file in C:\ //g = fname ? fopen(fname, "w+") : tmpfile(); if(NULL != fname) { g = fopen(fname, "w+"); } else { g = tmpfile(); } }
不能正常工作的代码
#ifdef _WIN32 # define tmpfile w32_tmpfile #endif FILE * w32_tmpfile ( void ) { code for Windows; } else if (code == 200) { // Downloading whole file /* Write new file (plus allow reading once we finish) */ // FIXME Win32 native version fails here because // Microsoft's version of tmpfile() creates the file in C:\ g = fname ? fopen(fname, "w+") : tmpfile(); }
也许你在一开始就看到了这个bug,但是作者没有。所有的问题都出在注释上:
/* Write new file (plus allow reading once we finish) */ // FIXME Win32 native version fails here because // Microsoft's version of tmpfile() creates the file in C:\
你看到了最后那个C:\吗?在C中,“\” 代表此行没有结束,于是,后面的代码也成了注释。这就是这个bug的真正原因!
而之所以改成if-else能工作的原因是因为作者注释了老的问号表达式的代码,所以,那段能工作的代码成了:
/* Write new file (plus allow reading once we finish) */ // FIXME Win32 native version fails here because Microsoft's version of tmpfile() creates the file in C: //g = fname ? fopen(fname, "w+") : tmpfile(); if(NULL != fname) { g = fopen(fname, "w+"); } else { g = tmpfile(); }
我相信,当作者找到这个问题的原因后,一定会骂一句“妈的”!我也相信,这个bug花费了作者很多时间!
最后,我也share一个我以前犯的一个错。
我有一个小函数,需要传入一个int* pInt的类型,然后我需要在我的代码里 把这个int* pInt作除数。于是我的代码成了下面的这个样子:
float result = num/*pInt;
…./* some comments */
-x<10 ? f(result):f(-result);
因为我在我当时用vi编写代码,所以没有语法高亮,而我的程序都编译通过了,但是却出现了很奇怪的事。我也不知道,用gdb调式的时候,发现有些语句直接就过了。这个问题让我花了很多时间,最后发现问题原来是没有空格导致的,TNND,下面我用代码高亮的插件来显示上面的代码,
float result = num/*pInt; .... /* some comments */ -x<10 ? f(result):f(-result);
Holly Shit! 我的代码成了:
float result = num-x<10 ? f(result):f(-result);
妈的!我的这个错误在愚蠢程度上和上面那个作者出的错误有一拼。
(全文完)
Web上的用户登录功能应该是最基本的功能了,可是在我看过一些站点的用户登录功能后,我觉得很有必要写一篇文章教大家怎么来做用户登录功能。下面的文章告诉大家这个功能可能并没有你所想像的那么简单,这是一个关系到用户安全的功能,希望大家能从下面的文章中能知道什么样的方法才是一个好的用户登录功能。以下内容,转载时请保持原文一致,并请注明作者和出处。
首先,我们先来说说用户名和口令的事。这并不是本站第一次谈论这个事了。如何管理自己的口令让你知道怎么管理自己的口令,破解你的口令让你知道在现代这样速度的计算速度下,用穷举法破解你的口令可能会是一件很轻松的事。在这里我想告诉从开发者的角度上来做设计这个用户名和口令的事。下面一几件规则:
首先,我想告诉大家的是,因为HTTP是无状态的协议,也就是说,这个协议是无法记录用户访问状态的,其每次请求都是独立的无关联的,一笔是一笔。而我们的网站都是设计成多个页面的,所在页面跳转过程中我们需要知道用户的状态,尤其是用户登录的状态,这样我们在页面跳转后我们才知道是否可以让用户有权限来操作一些功能或是查看一些数据。
所以,我们每个页面都需要对用户的身份进行认证。当然,我们不可能让用户在每个页面上输入用户名和口令,这会让用户觉得我们的网站相当的SB。为了实现这一功能,用得最多的技术就是浏览器的cookie,我们会把用户登录的信息存放在客户端的cookie里,这样,我们每个页面都从这个cookie里获得用户是否登录的信息,从而达到记录状态,验证用户的目的。但是,你真的会用cookie吗?下面是使用cookie的一些原则。
1)在cookie中,保存三个东西——用户名,登录序列,登录token。
用户名:明文存放。
登录序列:一个被MD5散列过的随机数,仅当强制用户输入口令时更新(如:用户修改了口令)。
登录token:一个被MD5散列过的随机数,仅一个登录session内有效,新的登录session会更新它。
2)上述三个东西会存在服务器上,服务器的验证用户需要验证客户端cookie里的这三个事。
3)这样的设计会有什么样的效果,会有下面的效果,
a)登录token是单实例登录。意思就是一个用户只能有一个登录实例。
b)登录序列是用来做盗用行为检测的。如果用户的cookie被盗后,盗用者使用这个cookie访问网站时,我们的系统是以为是合法用户,然后更新“登录token”,而真正的用户回来访问时,系统发现只有“用户名”和“登录序列”相同,但是“登录token” 不对,这样的话,系统就知道,这个用户可能出现了被盗用的情况,于是,系统可以清除并更改登录序列 和 登录token,这样就可以令所有的cookie失效,并要求用户输入口令。并给警告用户系统安全。
4)当然,上述这样的设计还是会有一些问题,比如:同一用户的不同设备登录,甚至在同一个设备上使用不同的浏览器保登录。一个设备会让另一个设备的登录token和登录序列失效,从而让其它设备和浏览器需要重新登录,并会造成cookie被盗用的假象。所以,你在服务器服还需要考虑- IP 地址,
a) 如果以口令方式登录,我们无需更新服务器的“登录序列”和 “登录token”(但需要更新cookie)。因为我们认为口令只有真正的用户知道。
b) 如果 IP相同 ,那么,我们无需更新服务器的“登录序列”和 “登录token”(但需要更新cookie)。因为我们认为是同一用户有同一IP(当然,同一个局域网里也有同一IP,但我们认为这个局域网是用户可以控制的。网吧内并不推荐使用这一功能)。
c) 如果 (IP不同 && 没有用口令登录),那么,“登录token” 就会在多个IP间发生变化(登录token在两个或多个ip间被来来回回的变换),当在一定时间内达到一定次数后,系统才会真正觉得被盗用的可能性很高,此时系统在后台清除“登录序列”和“登录token“,让Cookie失效,强制用户输入口令(或是要求用户更改口令),以保证多台设备上的cookie一致。
找回口令的功能一定要提供。但是很多朋友并不知道怎么来设计这个功能。我们有很多找回口令的设计,下面我逐个点评一下。
【感谢网友sumtec投递此文,很欢乐也有意思,与大家共勉】
首先说明:
1、以下特征是真实遇到过的,同事犯过的,乃至我自己也犯过的;
2、为了剧情需要,某些例子进行了一些夸张修饰等演绎创作,如无雷同,请勿生气;
3、如果你出现过以下症状之一,并不代表你就是弱爆了,但是如果你一直出现,乃至一说到这个大家就能联想到你,那么你就得小心了;
4、如果你是集这几个的大乘者,恭喜你,你已经找到了离开这个行业的充足理由了。
“那个Bug解决了吗?”
“好了,搞定!”
“这么快?”
正当你非常欣喜的时候,就传来了噩耗:刚才还能编译成功的,就失败了。(好吧,我们的集成编译尚未成功配置上,理论上这种事情应该会被退回。)又或者能编译成功,但是呢,原来明明能起作用的一个下拉框,突然发神经的不起作用了。最隐蔽的莫过于,一切正常,但是当你看到代码的时候,你就晕厥过去了。比如我们曾经发现了一个Bug,简单说就是每次用户点击某个东西,就会执行下面的这段C#代码:
controlPropertyPanel.PropertyChanged += this.UpdatePropertyOnChanged;
这个Bug很明显会导致速度越来越慢,因为同一个更新操作会被更新N次,并且这个N会越来越大。其实这个Bug已经够弱了,但是后来居然被修改为:
controlPropertyPanel.PropertyChanged -= this.UpdatePropertyOnChanged; controlPropertyPanel.PropertyChanged += this.UpdatePropertyOnChanged;
这段代码能编译,能执行,但是就是弱爆了。因为这不仅仅没有从根本上去掉造成问题的逻辑,还会带来更多的困惑:为什么要先减后加呢?
这类特征,请大家看看有趣的《各种流行的编程风格》,我这个例子算是一种撞大运。我觉这吧,这类问题都是因为只想解决一些表面的东西为目的,完全不管底下的其它任何问题而造成的。
“这个问题为啥还没解决呢?”
“我觉得应该是他那里边的Bug,我调不了。”
“哦……”
这个“他”可以是某一位同事,或者前同事,或者微软,或者别的什么公司,再或者某个开源代码的作者。这些个我都遇到过,比如说是另一位现在在职的同事吧。当你告诉这位同事这个Bug似乎在他那儿,并且问问什么时候解决,他也许会很愧疚的立刻调试,可最后结果却仍然是开头对话主人翁的所写代码的问题。
再比如说是微软吧,那么对话可能就会包括:“啊,SilverLight真是烂,老是内存泄漏、崩溃等……”“是啊是啊!烂死了!早知道用Flash了。”又或者会说:“微软就是烂,Java就是好。”其实,我不想比较什么SilverLight还是Flash,.NET还是Java。因为在讨论这些问题之前,先最好想想,这真的是别人的错么?相信是其他人的错是一件很简单的事情,因为这样推脱之后你就可以啥都不做了,反正不是我的错。
如果真的发现了这是别人的Bug并证明了,那倒好说。但这种特征是一种纯粹的怀疑,并没有丝毫的证明。在仔细找了自己所有可能犯的错之后,如果你怀疑是别人的问题,那请求证一下。
“楼主,无图无真相啊!”
“楼主,无代码无真相啊!”
“楼主,给翻译一下啊!”
据说Linus在别人询问Linux内存管理的一个什么问题时,回答道“Read the fxxxing source code”,很多时候我也有类似的冲动。我发现在信息发达的时代,不少人的阅读能力、动手能力都严重退化了。这些人最好就是你亲自来帮他把问题解决了,他才不想了解里面到底 发生了什么。这种问题体现在博客里面,就是寄希望于你写得图文并茂,图嘛最好花里胡哨同时言简而意概,文字嘛最好大段大段的代码。其实图不是重要的,只是为了好看,重点是代码,这样他一Copy就可以直接解决他们的问题了。
比方说,Silverlight里面没有各种图像格式的编码器,于是当你希望保存Jpg的时候怎么办呢?Google一下,发现原来有人写过一个FluxJpeg的编码器。下载下来一跑,唉还真能用哎。之后就直接签入,也不捎带看一下有没有什么问题,或者设计不合理的地方。(其实真的有,会很慢,因为有大量毫无必要的数组拷贝。)
又或者说,遇到了某个Bug,搜索一下发现,哎,还真有人遇到过,而且还有代码哎!把代码扒下来一跑,发现好像解决了,至于为什么就不管了。甚至还遇到过根本就不管解决不解决问题,反正代码扒下来了就签入了的。
再比如,写一篇博客讲解如何缩减.NET编译出来的文体大小,其中提到许多概念需要先阅读微软官方的一个文档。结果,还是会有人回复说,你那个文章里面提到那么多的Blob,也不说说Blob里面都有什么,大概是很不满意吧。可是这个文档里面都有啊,难道就不能自己阅读一下?其实即便我连这个文档都没有给出,自己也应该有这个能力去进行思考,去动手寻找。
千万不要退化成一个啥都要别人给你嚼烂了才能够吞下去,吞下去也不会消化吸收的人。这样的人大概别人给的是大便,只要有代码无真相,也会照样吃下去的。若真如此,那你打算如何提高呢?
“这个ExpressionVisitor,它是用来干什么的?”
“……”
“好吧,或者这么说,他是一个什么东西?”
“他是一个对象!”
“啊?”
“哦,是一个对象的实例。”
大概这样的回答,和那个微软工程师说“你在直升飞机上”差不多——反正你也不能说是错的,但是就是没什么意义。其实不知道没啥问题,人又不是神,怎么可能都知道呢?不去仔细了解和学习问题也不严重,因为你可以改。但是当你习惯性的随便找一个绝对没错但又不说明任何问题的答案,甚至似是而非的东西来对付的时候,你就离弱爆的边缘很近了。
当然,上面的对话也许是比较极端的。一个稍弱一点的对话版本是:
“这个内存泄漏是怎么造成的呢?”
“嗯,会不会是图片放的位置不对呢?”
哈,还是很夸张对吧?没办法,写博客有时候需要夸张的文字,否则你无法理解我的意思是:有时候,大家会倾向于从自己的记忆中寻找一些相似的物品,然后选择相似度自认为比较高的东西出来当作答案,而全然不管两者之间的逻辑是否有哪怕那么一丝的关联。也许很多时候,我们确实需要从相似的东西开始,但请别把他当作终点。程序是需要严谨的逻辑的,所以你也必须非常严谨的去推演。
关于这类的问题真的太多太多了,比如我指着下面这段代码当中的红字:
var dictionary = new Dictionary<string, string>();
dictionary["someKey"] = “someValue”;
“这句话说明了什么?”
“说明dictionary是一个数组。”
最后我举一个集大成者的例子,说,有个任务是要在SilverLight应用上面添加一个“收藏本站点”。好,怎么解决呢?网上一搜,发现有很多这样的代码:
function AddBookmark(Url, LabeName) { if (document.all) { window.external.addFavorite(Url, LabeName); } else if (window.sidebar) { window.sidebar.addPanel(LabeName, Url, ''); } }
然后直接扒下来就放上去了,通过某种方式在SilverLight中调用这段JavaScript,签入,搞定了!结果到了测试那边发现完全不能用,无论在IE6/7/8/9/10,还是在FireFox/Safari/Chrome上面,都不能使用。我问:
“这是什么原因呢?”
“不知道,反正浏览器报告没有权限,可能是浏览器的安全设置原因吧,或者操作系统的Bug,也可能是浏览器的某种Bug?”
“不可能啊?这些代码存在很多年了,要有问题早就能在网上搜索到了。”
“那也许是SilverLight调用的时候有什么安全问题。哎!SilverLight好烦啊!”
“那怎么还没有解决呢?”
“好,我马上解决它!”
很快,那段Javascript就变成了:
function AddBookmark(Url, LabeName) { try { if (document.all) { window.external.addFavorite(Url, LabeName); } else if (window.sidebar) { window.sidebar.addPanel(LabeName, Url, ''); } } catch { alert("您的浏览器因为安全设置的问题无法收藏,请手动添加收藏!"); } }
看到这样的代码,我彻底震惊了。亲自调试了一下,发现确实报告了一个“没有权限”的异常。但是,我还发现,那个Url参数的值是“www.adomainname.com\test\page.html”。那这不废话么!浏览器认为你要收藏的是一个本地硬盘上的路径,怎么可能在一个Internet Zone上允许收藏这种路径呢?我于是指着代码问:
“这个Url是什么?”
“是一个变量”
“啊?”
“哦,不对,是一个参数。”
你是否也有类似的经历呢?
(全文完)