第一次使用mac会遇到的一些典型问题

mac book air入手一周,算是可以进入工作状态了。遇到各种问题,各种google,学到了不少。现在把自己碰到的一些有代表性的问题写出来,希望能给刚开始使用mac的朋友们一些帮助。
  • 几乎所有的app的设置选项,都在屏幕顶部苹果logo右边,点击app名字弹出的菜单中,快捷键是Command+逗号
  • 在finder中按enter是重命名,打开文件夹和文件的快捷键是 Command+Down
  • 在finder中右键菜单中的“复制”是建立副本,“拷贝 xxx”才是复制
  • 在finder中没有剪切这个概念,如果需要移动文件,可以选中之后按 Command+C 拷
    贝,再进入目标目录,按Command+Option+V
  • 在finder的树形视图中选中某个文件夹并不代表进入了这个文件夹,如果想复制文件到该文件夹,需要双击或按Command+Down进入之后才能进行
  • f1 ~ f12键默认是mac自定义的功能键,需要同时按fn才生效,可以在系统偏好设置里更改

mac下配置php环境后出现syntax error

刚在mac上配置好php环境,运行项目代码就出现错误:

Parse error: syntax error, unexpected end of file in xxx.php on line 21

检查出错的文件,21行是该文件的最后一行,是一句很简单的php代码,没有发现任何问题。于是我准备用排除法来定位问题,将该行删除后刷新页面,错误信息变成:

Parse error: syntax error, unexpected end of file in xxx.php on line 20

错误行变成20行,而该行是一个html标签。这样就很奇怪了,非php的代码怎么会引发php的语法错误?

再继续排查,该文件中还有一段php代码:

<?php foreach ( $tags as $tag ) {
    ?><a href="<?= $tag[ 'url' ] ?>"><?= $tag[ 'name' ] ?></a>

虽然觉得不太可能,但还是将这段不能再简单的代码删除测试一下,居然不报错了!

继续测试,结果发现只要把循环体彻底删除,就不会报错。而只要保留循环体,哪怕是?><?,也会报错。脑中浮现出之前google时见过的一个诡异问题,莫非⋯⋯

打开php.ini,搜索short_open_tag,好吧,不知道出于什么目的,在用port装的php54里这个值居然是Off!貌似我用yum、apt-get乃至手动编译php,这个值都是On啊。

果断改之,重启php,问题顺利解决。

 

mac下利用国外的vps建立代理翻墙

现在很多人都有国外的VPS了,那么我们可以利用它建立SSH隧道代理来实现翻墙,解决很多国外网站上不了或间歇性不能访问的问题。

1、首先到http://nihilex.com/secret-socks去下载SecretSocks并安装。

2、运行之后,在SSH hostname中输入自己vps的ip,在Port number中输入ssh的端口(如果你没有改过的话是22),然后在Username中输入可用ssh登录的用户,在Password中输入密码,然后点击Connect按钮。如果看到绿色的对号,说明连接成功了。

3、配置浏览器中的代理,不过我的chrome和safari都自动发现了代理,不需要特别配置就可以使用了。如果你使用的浏览器没有,那么就手动设置一下代理服务器为127.0.0.1:9999。

需要注意的事项:只在需要翻墙的时候Connect,不需要翻墙时记得Disconnect,否则访问国内的网站会很慢或打不开,而且VPS的流量一般是有限的,需要节约使用。

不引入依赖库,细粒度管理js代码

在一个项目刚起步时,往往除了jquery之类的库外,只有一个js文件。

然而随着功能的不断增加,js代码也会不断膨胀,当多到一定程度后(500行以上),维护难度就会大大增加。组织逻辑混乱,debug时很难迅速定位到想修改的地方,写新function时也很难抉择插入的位置。整天在一个长长的js文件里滚来滚去,想想都让人头疼。

一个可选的方案是使用依赖库,诸如requireJS、SeaJS、In.js等等。但需要引入更多的代码,要抽出精力来维护依赖关系,还会增加额外的http请求。

在大多数情况下,js代码不会膨胀到必需引入依赖库的地步。有没有办法可以在不增加新代码和http请求的前提下,对js代码进行细粒度的维护呢?

在目前的项目中,除去jquery等类库后的js代码约有120kb(压缩后58kb),我们使用这样的方案:

1、按模块或者按性质将js拆分成多个文件,比如将和用户相关的部分抽取出来成为user.js、共用的方法抽取出来成为helpers.js等。

2、编写sh脚本(linux)或bat脚本(windows)合并所有js为all.js,并调用yuicompressor进行压缩生成all-min.js。

//windows下的bat脚本
del all.js all-min.js
type source\*.js > all.js
java -jar yc.jar --preserve-semi -o all-min.js all.js

3、在开发环境使用all.js(方便deug),生产环境使用all-min.js。

 

用Win+R启动常用软件及文档,提升工作效率

我们都试过用开始菜单、桌面图标、快速工具栏、辅助桌面管理软件等来管理常用软件,在Win7下还可以直接固定到任务栏,然而这些方法都有或多或少的瑕疵:

开始菜单:第一级可用位置太少,多级的话查找太慢
桌面图标:软件多了之后太乱,难以整理
快速启动栏 / 固定到任务栏(Win7):挤压占用任务栏空间
辅助桌面管理软件:需要安装多余的软件,可定制性差

试验过以上所有方法后,我选择了使用StokeIt以及Win+R法来管理和启动常用软件。这篇文章就来简单介绍一下我使用Win+R法的一些经验。

Win+R法简介

通过在环境变量PATH中添加一个目录,然后将常用软件及文档的快捷方式(.lnk文件)重命名为1~2个字母缩写放入该目录,要启动软件时按Win+R启动“运行”对话框,然后键入自定义的缩写再按回车即可。

详细的入门教程请看善用佳软的文章,本文只总结自己的一些经验。我的习惯是在D盘下建立一个Command目录,然后将所有快捷方式都放在此目录下。

创建对自身的引用

这个目录要经常维护,所以先要为自己建立一个快捷方式,我将其命名为go,并放到Command目录下。之后要维护快捷方式直接Win+R go回车即可。

分组管理快捷方式

在“运行”对话框里是可以直接打开环境变量PATH中定义过的目录下的子目录的,所以在Command目录下可以建立目录对常用软件进行分组。比如我因为工作需要安装了大量的浏览器,而这些浏览器只有在测试的时候才用到,所以我不想给每个浏览器都建立一个缩写,而是建立一个br(browser的缩写)目录,并将所有浏览器的快捷方式都放在br目录下。在需要测试的时候Win+R br,然后选择启动需要的浏览器。

使用批处理

在Command目录下编辑BAT批处理(类似Unix下的shell脚本),可以实现更强大的组合功能,比如创建一个这样的test.bat,使用start命令来一次运行多个软件:

start cr.lnk
start ie.lnk
start ff.lnk

这样通过Win+R test可以同时打开chrome、ie和ff三个浏览器(当然前提是要在Command目录下建立好cr、ie、ff这三个快捷方式)。注意在命令行模式下是不能省略.lnk后缀的。

快速连接远程桌面

第一步:在“运行”中启动mstsc,点击“选项”,输入ip地址,取消勾选“始终要求凭据”。连接并登录一次。

第二步:在C:\Windows\System32目录下找到mstsc.exe,创建一个快捷方式到Command目录,改一个合适的名字(建议以ip最后一节命名,比如ip为10.0.0.88,则命名为88)。右键编辑其属性,在“目标”中的System32\mstsc.exe后加上格式为“/v:IP地址:端口号”的参数,比如“System32\mstsc.exe /v:10.0.0.1:3389”(如果没有修改过远程端口号,也可以省略后面的冒号和端口号)。当然也可以创建BAT文件,里面写上start mstsc /v:10.0.0.1:3389)。

之后只要Win+R 88就可以连接到远程桌面了。

管理网站账户密码

有的网站可能一年半载去不了一次,但是还是需要注册登录(比如csdn下载站-_-)。大多数情况下不同网站下的用户名都会有所不同(比如zhangsan123,zhangsan1986等),一般对这种不重要的网站我们都会使用比较容易输入的短密码,但由于各网站密码规则的不同,最终密码还是会不一样。记忆这些用户名和密码纯粹是浪费脑细胞!

这时最好的方法莫过于建立一个密码本了,在需要登录的时候打开看一眼或者直接Copy & Paste。我的习惯是在Command目录下建一个p目录,然后在里面建立txt文件,文件名为网站域名(注意去掉www.),内容为用户名和密码。

这样当我要登录一个之前注册过的网站时,只要Win+R p,然后输入域名前几位,就可以迅速定位到对应的文件并打开,拿到帐号和密码了。

当然了,你轻松,别人也轻松。比较重要的密码(如qq、邮箱、网银)不能放在这里!

使用StrokeIt提升工作效率

StrokeIt是一款鼠标手势软件,可以将用鼠标右键画出的手势绑定到许多常用的操作,如关闭/最大化窗口、启动程序、触发快捷键等。

下载地址和新手入门可见善用佳软的这篇文章,我就不再重复了。

这里和大家分享一下我使用StrokeIt的一些心得吧。刚接触这款软件时,有点走火入魔,把所有常用软件都绑定到手势,恨不得把所有的内置手势都利用上,甚至还自创了一些手势。结果是大部分的手势都很不常用,加之后来采用了WIN+R法启动常用软件(随后撰文另述),启动软件的手势慢慢就被淘汰了。

最终经过时间的考验,存活下来的手势只有一屏而已:

简单说明一下吧:

C Reversed(从下往上画的C):启动上图中的命令编辑器(Command:StrokeIt – Command Editor)。这在刚开始使用StrokeIt的那段适应期里是使用很频繁的功能。。

C (从上往下画的C):关闭窗口(Command:Keys – Hotkey,Alt+F4)。直接关闭当前窗口,主要用于多标签支持的程序。

\Up(从右下到左上):关闭一个窗口或标签(Command:Keys – Hotkey,Alt+F4)。在单一的程序窗口执行的是Alt+F4,即直接关闭当前窗口。在有多标签支持的程序里(如浏览器、Eclipse、PS等,在tabs分类中进行了重载)执行的是Ctrl+F4,即关闭当前标签。这样省去了鼠标定位关闭按钮(一般都很小)的时间。

\Down(从左上到右下):打开QQ消息(Command:Keys – Send Keystrokes,[F1])。恩没错,我用的是F1,这样左手也可以很方便得启动。你也可以设成Ctrl+Alt+Z或者你自己的快捷键。不用Hotkey的原因是在Hotkey里按F1会弹出帮助,无法记录下按键,下面使用Keystrokes的情况都是这个原因。

/Up(从左下到右上):最大化窗口/将窗口恢复正常(Command:Windows – Maximize or Restore)。这在窗口管理中是使用频率仅次于关闭的功能,就不解释了。

/Down(从右上到左下):最小化所有窗口并显示桌面(Command:Keys – Send Keystrokes,[WIN_DOWN]d[WIN_UP])。相信这个也不用解释了吧。

T(从左上到右上,再到右下):启动记事本(Command:Run -Run Program,C:\Windows\notepad.exe)。这个是我最初绑定的大量启动程序类手势中唯一保留的一个,平时用来快速记点什么东西、格式化文本转纯文本等,很方便快捷。

Left(从右向左):返回父级目录(Command:Keys – Hotkey,Alt + Up)。在Windows的资源管理器里按Backspace或者后退,是到历史记录里的上一条,而不是到当前目录的父级。这样我们通过直接输入目录地址,以及通过“打开文件位置”进入的目录,就要按Alt+Up这个很少有人知道的快捷键。

V(左上到下,再到右上):访问指定目录(Command:Run – Run Program,C:\Windows\explorer.exe)。通过手势打开目录的方法就是启动资源浏览器explorer.exe,并把要打开的目录地址(这里是\\server)通过参数(/e, “目录地址”)传递给它。

最后再说一下怎么给分类增加程序(用来重载)。比如我们有一个支持多标签的新软件要加入到tabs分类中,那么先选中tabs分类,然后点击右边的Add按钮:

启动你要添加的程序,把Finder Tool下面的那个图标拖动到你要添加的程序窗口上(如果是全屏运行的程序,那就只能通过File Name进行添加了,需要到程序安装目录下找出主程序文件名)。

获取到Window Class后按OK,在Application Identifiers里出现了新的CLASS,这个程序就成功地添加到分类里了。如果要从分类中移除该程序,将CLASS删除即可。如果忘了是哪个CLASS,可以通过Finder Tool重新查看。

我所信奉的编程哲学

从去年到今年,陆陆续续看完了《代码大全》《重构》《代码整洁之道》《程序员修炼之道》以及《The Art of Readable Code》,获益匪浅。下面就分享几条我赞同并信奉的编程哲学,顺便废话几句。

本文适合有一定编程经验的读者阅读,高手请轻喷:)

一、代码是写给人看的(Coding for Reading)

请先思考,评价一段代码优劣最重要的标准是什么?

有个著名的图,相信大家都见过,讲的是Code Review时被骂WTF的次数越少,代码的质量就越高。这虽然有点无厘头,但是却不无道理。

《The Art of Readable Code》中告诉我们,评价一段代码的质量的最佳标准是可读性,即别人理解代码意图所需要的时间。

可读性有什么用,我写出一段代码,能完成目标,能通过测试不就行了吗?

不管你是团队开发,还是一个人单干,只要项目还在运作,代码总是要有人来维护的。如果有一天你不在或者离开了,别人应该能很轻松的看懂你的代码,而不是猜来猜去,最后还要来问你,甚至弃用整段代码重新再写。作为一个负责任的开发者,应该尊重自己的劳动成果,也尊重别人的时间。

当你写下一段代码时,脑子里有清晰的逻辑(比如这里必须这么做,因为……这里不能那么做,因为……)。而别人看这段代码时,脑中是一片空白(这个变量是干什么的?为什么要多加一层判断?这个数为什么是 2 不是 3 ?),除非能完整复现你当时脑中的逻辑,否则就不能理解这段代码。

所以我们应该做的是,把写代码时脑中想到的逻辑的每一个细节,尽可能地写到代码以及注释中,从而帮助后来的阅读者快速复原整个逻辑。简单来说可以有以下的方法:

用常量代替魔术数

在一段代码中出现 if ( level > 3 ) …的时候,也许你会想:为什么是 3 不是 4 呢?这就是所谓的魔术数。通过上下文也许你可以判断出这个 3 是指最高等级,但是可能这段代码中 3 出现了好几次。你会迷惑:它们是不是都是一个意思?当我要把最高等级改成 4 的时候,是不是应该修改所有的 3 ?

如果代码的作者定义一个常量MAX_LEVEL = 3,同时还有一个常量USERS_PER_PAGE = 3,并在不同的地方使用不同的常量,就不会有这样的混淆了。

使用富含信息的类名、变量名和函数名


doit();
$return = $str2 . ':' . $str3;
return $return;

这样的代码,简直就是人工混淆过的。你会发现这些代码根本不能帮助你理解代码的含义。

也许你该写成这样?


handleError();
$error_message = $error_code . ':' . $error_status;
return $error_message;

在注释中说明一段代码存在的原因,而不是行为

$('#item').html(''); //清空item的内容

这样的注释有意义么?用自然语言重新描述一次代码的行为,除了徒增维护时的工作量外没有任何价值。你应该说明为什么这样做,以供别人看到这段代码时明白你是怎么想的,并决定如何修改或者对待这段代码。

$('#item').html(''); //先清空容器的内容,否则可能导致内容重复

二、写得越少越好(Less is More)

这个规则的使用性太强了,我简单说说减少逻辑层次和缩小函数体这两个方面吧。

减少逻辑层次

当逻辑层次超过三层时,理解这段代码的难度会急剧上升。我相信谁也不喜欢去读一个n层括号的表达式,或者面对n层缩进的条件判断/循环。

对于复杂的表达式,通过提取中间变量来降低表达式的逻辑层次,保证每个表达式的逻辑层次不超过二层。

对于多层条件判断,大多数情况可以用防御式编程将其简化成单层的条件判断,尽早return或者exit。此外,单行的if-else判断往往可以用三元操作符替换。如果判断实在太多,也许你该重新设计一下结构了。

缩小函数体

记得有一位语言的创始人说过:“我不喜欢比我的头还大的函数”。

事实上,大家都喜欢短小精干、一眼就能看到底的代码。简洁明快的代码有助于别人迅速理解代码的意图,也方便快速定位问题。如果一个函数要滚动屏幕才能看全,那你往往要不断地来回滚动,并强迫自己记住一些信息,再返回去看另一部分,这样做会非常累。

随着函数体不断膨胀,理解它所需要的时间随之增加,出错的几率也会大大提升。而且越大的函数,可维护性和复用性越差。当部分代码逻辑需要修改时,不能快速定位到要修改的位置,也难以确定函数体其他位置是否也需要对应的修改。

当函数尺寸失控时,首先要想到的是,有没有其他方法,用更少的代码完成这个任务?能不能用正则表达式?能不能用查表法?有没有内置的库函数可以利用?

面对一个无法再简化的流程,将其拆分成细粒度的步骤,将每个步骤的相关代码分离出来,提取成子函数,再给子函数起一个漂亮的名字。这样可以降低理解主流程的难度,在做修改时也可以通过函数名快速定位,而且因为相关的代码都在一起,不容易漏改。

也许你会怀疑调用函数所造成的性能损失,我想说现在这个时代,手机都马上四核了……

三、不要重复(Don’t Repeat Yourself)

看过《重构》一书后,我看到代码中任何重复的地方都如见眼中钉。

重复是万恶之源,当你发现你在对代码的不同部分进行同样的修改时就要警惕了。改的地方越多,就越可能出错。也许忘了改一个地方,也许错改或者删除了周边的代码……永远不要让这种事情发生!

将重复的代码提炼成子函数


$('#count').text( + $('#count').text() + 1 );
...
$('#count').text( + $('#count').text() + 1 );
...

如果一段相同的代码出现两次,基本上你还会第三次用到它,所以很有必要将其提炼成子函数。这样不仅可以减少代码量,还可以降低维护的难度。


countPlusOne();
...
countPlusOne();
...
function countPlusOne() {
$('#count').text( + $('#count').text() + 1 ); //以后修改只用改这里就好了
}

如果有几段代码很相似,往往可以提取其共性逻辑,使用不同的调用参数进行区分。


countChange( +1 );
...
countChange( -1 );
...
function countChange( change ) {
$('#count').text( + $('#count').text() + change );
}

用循环减少重复


$item[ 'id' ] = $_POST[ 'id' ];
$item[ 'name' ] = $_POST[ 'name' ];
$item[ 'mail' ] = $_POST[ 'mail' ];
$item[ 'qq' ] = $_POST[ 'qq' ];

如果有大块逻辑雷同只有一两个地方更改的代码,往往可以用循环来解决:


foreach ( array( 'id', 'name', 'mail', 'qq' ) as $key ) {
$item[ $key ] = $_POST[ $key ];
}

说说qwerty、dvorak、colemak三种键盘布局

【qwerty布局】


qwerty布局大家应该都很熟悉了,全世界最普及的键盘布局。
截止到去年接触并使用dvorak布局之前,我使用了十几年qwerty布局,在http://speedtest.10fastfingers.com/测试的最高CPM(每分钟击键数)为475。
相信阅读本文的读者中,有99%以上都在使用qwerty布局。

热区图


本热区图是使用http://www.patrick-wied.at/projects/heatmap-keyboard/对apache2的license.txt(36k)进行分析的结果,下同

优点

1、普及率和兼容性接近100%
2、有史以来的各种软件的快捷键都默认为此布局设计
3、最常用的快捷键(^a, ^s, ^c, ^v, ^x, ^z, ^w)都可用左手完成

缺点

1、高频键大多不在中排,都需要移动手指才能按到,导致需要大量的手指移动
2、高频键左右分布不均衡,左手负担比右手重
3、经常出现单手连续击键(尤其是左手)的情况,容易疲劳也容易出错
4、常用标点符号;/’都要用小指输入,容易疲劳也容易出错

【dvorak布局】


这个布局知道的人就很少了,和qwerty布局相比,它将元音字母aoeui移到左边,将最常用的辅音字母移到右边,同时调整了一下标点符号的键位。
自从去年接触到它后,我用了一个月的时间适应,然后就抛弃qwerty布局了。不仅输入速度得到了提升(在http://speedtest.10fastfingers.com/测试的最高CPM为535),而且长时间coding也很少出现疲劳的情况了。

热区图

使用

Windows:在语言设置里添加“美国英语-Dvorak”,推荐使用http://code.google.com/p/dvorak-qwerty/
Linux:在终端输入loadkeys dvorak(GUI请在键盘设置里找),推荐使用http://code.google.com/p/dvorak-qwerty/
Mac:在语言设置里选择“Dvorak-标准”
更多系统下启用Dvorak布局请见:http://dvorak.mwbrooks.com/support.html

优点

1、将高频键都分布在中排,大大降低手指移动距离
2、尽可能使左右手交替击键,均衡负担
3、布局优雅,精心设计了右手负责区域的键位,使辅音字母组合(如th nt gh wh rn)输入非常顺手
4、将常用的标点符号’ , . ; = – 移到更舒服的位置,输入更方便(对程序员来说巨爽)。

缺点

1、布局变化过大,几乎所有的字母和标点符号都改变了,上手困难
2、到qwerty布局的电脑上丧失盲打能力
3、别人很难再使用你的电脑了,可以再装一个qwerty布局切换
4、本来单手可以操作的快捷键要两手操作。不过MacOS下有天然的“Dvorak-标准”布局,Windows和Linux下可以用这个http://code.google.com/p/dvorak-qwerty/来使用原有键位的快捷键,但是还有一些BUG(不支持ALT快捷键、有个别字母如s w v z在开启Capslock时输入仍是小写等)
5、部分跳过键盘布局直接取键盘码的软件仍然是qwerty布局,比如QQ拼音和搜狗输入法(经测试紫光和谷歌输入法可以正常使用,只是可惜了我的QQ同步词库),还有QQ和部分网银的密码输入框(解决办法是看着键盘按qwerty布局输入),还有一些远程桌面软件,还有一些游戏(比如魔兽,这点挺好,DOTA无障碍了,哈哈)
6、高频键 r 和 i 仍需要移动才能按到,而 u 和 h 不是很高频的键却放在了本位(不需要移动就能按到)

【dvorak programmer布局】


估计某个程序员发现用dvorak布局很爽,就将之变得更加极限。于是出来这么一个布局。
这个布局大胆的用常用符号替换了数字,同时考虑到在程序中;比”出现得更频繁,调换了两者之间的位置。

热区图

 

优点

1、包括上述dvorak键盘的所有缺点
2、优化了程序中常用符号如$ ; ( ) { } [ ] =等,用来coding巨爽

缺点

1、包括上述dvorak布局的所有缺点
2、输入数字只能按住shift进行-_-

【colemak布局】


这个布局比dvorak更激进一些,通过热区图可以看到,8个高频键完美地安排在了本位。
坚持使用colemak数日后,实在无法接受别扭的标点输入进行coding,只好放弃colemak,用回dvorak了,也许colemak对普通人来说更适合一些。

热区图

使用

Windows:http://colemak.com/wiki/index.php?title=Windows
Linux:http://colemak.com/wiki/index.php?title=Unix
Mac:http://colemak.com/wiki/index.php?title=Mac
更多资料请见:http://colemak.com/

优点

1、高频键真正做到了完美分布,手指移动距离更小
2、qwzxcvbm以及标点符号键都没有移动,而且除了e和p外,字母键都在原来的手控制之下,更容易上手
3、常用的快捷键(^a, ^c, ^v, ^x, ^z, ^w)仍能正常使用
4、万年不用的capslock被换成了高频键backspace,巨爽
5、有资料详尽的官方网站http://colemak.com/,甚至针对各种打字练习软件设计了练习包

缺点

1、键分布没有逻辑,不如dvorak优雅
2、没有优化常用标点符号

在MongoDB的shell中显示服务器当前IP

不久前发生一起严重的事故,误删了生产MongoDB服务器上的一个collection,由于没有定时备份,导致几天的数据丢失。认真反省之后,写下这篇文章,希望能给大家一些警示。

过程

当时我正双开两个终端窗口,分别用mongo连接开发环境和生产环境的MongoDB。由于本地的数据库因为不断的开发测试已经被污染了,我从生产环境dump了最新的数据并下载到本地。接下来要做的是把本地MongoDB中的对应collection给drop掉,然后restore回去。

这时我不知道被什么事情打断了,当我继续这个过程时,由于Mongo Shell中没有像Redis那样显示服务器的IP,我并没有注意到自己点开的是生产服务器的终端,并执行了drop命令……

反省

1、在对生产数据库做删除之类的危险操作时,一定要两个人double check;
2、明确区分生产环境和开发环境,在执行各种删除操作前对所在的环境进行确认;
3、在执行重要操作时,不接受其他事务的打断;

措施

在生产环境和开发环境都建立一个~/.mongorc.js文件,内容如下:

var prompt = function ( ) {
var host = db.getMongo().toString().replace( 'connection to ', '' );
var database = db.getName();
return host + '/' + database + '> ';
}

然后连接到Mongo Shell时就会显示服务器IP了,如:

[zhangshenjia@mac: ~]$mongo
MongoDB shell version: 2.4.4
connecting to: test
127.0.0.1/test>

防止在iOS设备中的Safari将数字识别为电话号码

在测试中发现iPad上的Safari总会把长串数字识别为电话号码,文字变成蓝色,点击还会弹出菜单添加到通讯录。

别的地方倒也罢了,如果在用户名中出现数字(手机注册新浪微博的话用户名就是“手机用户xxxxxxxx”),版式会很恶心。

经过测试在a标签中的长串数字不会识别为电话,于是给出现用户名但没有链接的地方嵌套一个无动作的a标签,临时解决了这个问题。

但是这样增加了额外的标签,代码的语义性变得很差,而且对大段文字不能用这个方法。

今天无意中撞进Safari的官网,发现了safari有个私有meta属性可以解决这个问题:


官网的说明如下:

How do I disable automatic detection of phone numbers in webpages?

In Safari on iPhone, phone numbers are automatically detected and transformed into links that dial the phone number when tapped. If you have strings of numbers in your webpage that should not be automatically detected as phone numbers, you can choose to disable this feature on the entire page by adding the <meta> tag shown in Listing 12.

取得URL相对于另一个URL的相对路径

今天群里灰大出了个题:

function getRelativeURL(url, baseURL) {
   // 获取url相对baseURL的相对路径
   // url和baseURL都是绝对路径或都是相对路径的情况下,有可能计算出结果,否则扔Error
   // 如果都是绝对路径,但不在同一个域、同一个协议、同一个端口下,即无法计算相对路径,扔Error
}

用了点时间写了一下:

function getRelativeURL ( url, baseUrl ) {

    var path, 
        basePath,
        i = 0,
        compare = {},
        r = {    'proto' : /(^[a-zA-Z]{1,4}):\/\/(.*)/,
                'domain' : /(^[-_a-zA-Z.]+)((?:\/|:|$)?.*)/,
                'port' : /(?:^:)(\d+)((?:\/|$).*)/,
                'word' : /[^, ]+/g,
        };

    compare[ 'url' ] = parseUrl( url );
    compare[ 'baseUrl' ] = parseUrl( baseUrl );

    if ( different( 'proto' ) || different( 'domain' ) || different( 'port' ) ) return false;    //协议、域名、端口不同

    path = compare.url.path;
    basePath = compare.baseUrl.path;

    while ( path.length && path[ 0 ] == basePath[ 0 ] ) { //去除相同父目录
        path.shift();
        basePath.shift();
    }

    if ( path.length == 0 && basePath.length == 0 ) return './';     //两个url相同
    
    if ( path.length == 0 ) return parent( basePath.length );        //baseUrl是url的子目录

    if ( basePath.length == 0 ) return path.join( '/' ) + '/';        //url是baseUrl的子目录

    return parent( basePath.length ) + path.join( '/' ) + '/';        //url和baseUrl互不包含

    function different ( name ) {
        return compare[ 'url' ][ name ] != compare[ 'baseUrl' ][ name ];
    }

    function notValidUrl () {
        return compare[ 'url' ][ 'proto' ] == '' || compare[ 'baseUrl' ][ 'proto' ] == '';
    }

    function parseUrl ( url ) {
        var parsed = {};
        'proto,domain,port'.replace( r.word, function( name ){
            var match, reg = r[ name ];
            if ( !reg.test( url ) ) {
                parsed[ name ] = '';
                return;
            }
            match = url.match( reg );
            url = match[ 2 ];
            parsed[ name ] = match[ 1 ];
        });
        parsed[ 'path' ] = url.replace(/\/+$/,'').split('/');
        return parsed;
    }

    function parent ( level ) {
        console.log(level);
        var result = [];
        while ( level-- ) result.push( '../' );
        return result.join('');
    }
};

使用单元测试如下:

test("url和baseURL并非都是绝对路径或都是相对路径,没有结果", function() {
    expect(4);
    ok(!getRelativeURL('http://aoeu/ao','/aoeu'),'绝对,相对');
    ok(!getRelativeURL('/aoeu','http://aoeu/ao'),'相对,绝对');
    ok(getRelativeURL('http://aoeu','http://aoeu/ao'),'绝对,绝对');
    ok(getRelativeURL('/aoeu','/ao'),'相对,相对');
});
 
test("不在同一个域、同一个协议、同一个端口下,没有结果", function() {
    expect(3);
    ok(!getRelativeURL('http://aoc.com','http://aoeu.com'),'不同域');
    ok(!getRelativeURL('https://aoc.com','ftp://aoeu.com'),'不同协议');
    ok(!getRelativeURL('https://aoc.com:88','ftp://aoc.com:89'),'不同端口');
});

test("是否以/结尾不影响结果",function() {
    expect(3);
    equal(getRelativeURL('http://aoeu.com/aoeu/','http://aoeu.com/'),getRelativeURL('http://aoeu.com/aoeu','http://aoeu.com'),'都以/结尾');
    equal(getRelativeURL('http://aoeu.com/aoeu/','http://aoeu.com'),getRelativeURL('http://aoeu.com/aoeu','http://aoeu.com'),'url以/结尾');
    equal(getRelativeURL('http://aoeu.com/aoeu','http://aoeu.com/'),getRelativeURL('http://aoeu.com/aoeu','http://aoeu.com'),'baseUrl以/结尾');
})

test("相同目录",function(){
    expect(3);
    equal(getRelativeURL('http://aoeu.com','http://aoeu.com/'),'./','根目录');
    equal(getRelativeURL('http://aoeu.com/aoeu','http://aoeu.com/aoeu'),'./','单级');
    equal(getRelativeURL('http://aoeu.com/ao/eu','http://aoeu.com/ao/eu/'),'./','多级');
})

test("url和baseUrl中有一个为根目录",function() {
    expect(4);
    equal(getRelativeURL('http://aoeu.com/aoeu','http://aoeu.com'),'aoeu/','url为baseUrl的单级子目录');
    equal(getRelativeURL('http://aoeu.com/ao/eu','http://aoeu.com'),'ao/eu/','url为baseUrl的多级子目录');
    equal(getRelativeURL('http://aoeu.com','http://aoeu.com/ao'),'../','baseUrl为url的单级子目录');
    equal(getRelativeURL('http://aoeu.com','http://aoeu.com/ao/eu'),'../../','baseUrl为url的多级子目录'); 
!
})

test("其他",function() {
    expect(2);
    equal(getRelativeURL('http://aoeu.com/aoeu','http://aoeu.com/ueoa'),'../aoeu/','单级');
    equal(getRelativeURL('http://aoeu.com/ao/eu','http://aoeu.com/eu/ao'),'../../ao/eu/','多级');
})

如有更好解法或发现错误,请不吝赐教:)

aoeu.sh 让你 type less, do more

Github: https://github.com/shenjia/aoeu.sh

这是一个为 Linux / MacOS 用户设计的alias集合。

 作为开发人员,我们每天都要在终端敲很多重复性的命令。一些大段重复的命令(比如部署,安装,备份等)我们会写shell脚本来避免重复性的键入,却忽视了还有很多使用频率极高的命令。 Continue reading aoeu.sh 让你 type less, do more

解决google maps api阻塞页面渲染的问题

google maps的api使用很方便,只要在用使用google maps的页面引入http://maps.google.com/maps/api/js?sensor=false就可以了。


但是在项目中这样使用后,发现domReady事件被严重阻塞(甚至会延迟十几秒)

因为domReady只有页面中所有的script都运行完后才会触发,而google的服务器往往很慢……直接用script标签来加入这个壳,会使domReady变得不可控。

为了防止阻塞domReady,对于不是必须在渲染前执行的js代码,一般使用document.createElement创建script标签,这样浏览器会在domReady后再下载这些script并执行。我一般使用这样的代码:

$.loadJs = function ( src ) {
    var script = document.createElement( 'script' );
    script.type = 'text/javascript';
    script.src = src;
    document.body.appendChild( script );
}

在使用$.loadJs加载http://maps.google.com/maps/api/js?sensor=false后,domReady正常了,但是google maps却不能正常使用了。

审查元素发现,壳虽然被引入了,但是依赖的子模块都没有引入进来,于是我们检查壳里加载子模块的代码:

window.google = window.google || {};
google.maps = google.maps || {};
(function() {
    function getScript(src) {
        document.write('<' + 'script src="' + src + '"' +
                   ' type="text/javascript"><' + '/script>');
    }
    .....

乖乖,居然用document.write。我们知道document.write只有在输出流还没有闭合的时候才能输出内容,而我们用$.loadJs方法加载壳的时候,输出流早就关闭了。

这个怎么解决呢,总不能改google的代码吧?曾经想过把这个壳存到本地,修改后使用,但是谁知道google哪天修改了什么东东呢?

仔细分析了一下,这个壳实际上只是设定一些参数,然后用document.write加载另一个壳http://maps.gstatic.com/intl/zh_cn/mapfiles/api-3/7/11/main.js,实际加载模块的动作是在后者中进行。

万幸,后者加载子模块使用的是document.createElement方法……

冥思苦想后,想出一个hack的方法:在调用第一个壳之前将原生的document.write方法保存起来,将其改写为调用$.loadJs,调用之后恢复原有的方法(因为write只是用了一次)。

$.writeListener = function ( ) {
  var write = document.write;
  document.write = function ( html ) {
    var scriptPattern = new RegExp( ']*src=[\'"]([^\'"<>]*)["\'][^>]*>', 'i' );
    if ( scriptPattern.test( html ) ) {
      src = html.match( scriptPattern )[1];
      $.loadJs( src );
      document.write = write;
    } else {
      write.call( document, html );
    }
  }
}
$.loadJs( 'http://maps.google.com/maps/api/js?sensor=false' );

暂时只能想到这里,哪位有更好的解决思路,请不吝赐教。

IIS无法启动(1058错误)的解决办法

前段时间配置服务器,更换IIS服务启动用户后就无法启动IIS服务了,提示1058错误(服务被禁用或依赖的服务没有启动),查看之后发现依赖的两个服务Remote Procedure Call和Security Accounts Manager都正常启动,服务也没有禁用,奇怪了……把启动用户改为本地系统帐户后仍然无法启动,重启也不行,只有重装IIS……解决问题后百思不得其解,最终无意中发现服务属性中“登录”页里硬件配置文件“Profile 1”后提示“已禁用”,将其启用后服务一切正常,这才恍然大悟,微软真是隐蔽,在这里还藏了一手,差点被忽悠了……希望能对遇到同样问题的朋友有所帮助。

用Cookie保存和恢复进度条

有时候我们需要在一个较长的列表中选择并刷新页面,刷新后页面会跳转到最顶部,当需要连续选择的时候就会很麻烦,每次选择后都要滚动到原先位置再选择下一个。

还有在分页程序中,如果一页显示的记录比较多,导致翻页后要滚动屏幕才能点击到翻页按钮,那么每次翻页后都要滚动屏幕,当需要连续翻页的时候就会很麻烦。

在做目录树的时候我们可以用无刷新技术,但是比较麻烦,而且在记录集分页显示的时候用无刷新技术明显是不可能的。

有鉴于此,我通过Cookie实现了保存页面进度条情况,每次跳转后自动跳转到原先进度的功能,希望对一些朋友们有用。

Scroll.asp如下:

<%
‘————————
‘Project By BTwork Studio
‘Code:[BT]Ok
‘Date:2006-4-4
‘————————
%>
<script language=”javascript”>
function SaveScroll()
{
//保存进度条情况
document.cookie = “Scroll=” + escape(document.body.scrollTop);
}

function ReadScroll()
{
//读取进度条情况
var aCookie = document.cookie.split(“; “);
for (var i=0; i < aCookie.length; i++)
{
var aCrumb = aCookie.split(“=”);
if (“Scroll” == aCrumb[0])
return unescape(aCrumb[1]);
}
}

function GoScroll()
{
//跳转进度条
var Scroll;
Scroll = ReadScroll();
window.scrollTo(0,Scroll);
}

function ClearScroll()
{
//清空进度条
document.cookie = “Scroll=0; expires=Fri, 31 Dec 1999 23:59:59 GMT;”;
}
</script>

————————————————————————

在页面首部include调用Scroll.asp,然后在修改<body>:

<body onLoad=”<%If Request(“Page”) = 1 Then%>ClearScroll()<%Else%>GoScroll()<%End If%>”>

其中Request(“Page”) = 1是初始化条件,你可根据情况更换。当设定条件成立时将清空Cookie中的进度条。

最后修改翻页的连接:

<a href=”?page=<%=Page+1%>” onclick=”SaveScroll()”>上一页</a>

大功告成,试试吧。