善意提醒

如果您打开本站很慢,布局排版混乱,并且看不到图片,那么可能是因为您还没有掌握用科学的方法上网的本领。

2017-06-22

防贼不编年史

Chapter One

很早的时候,赛博世界还只有病毒,没有贼。
那时候,有的病毒还可以和人类和睦相处,称之为「良性病毒」。
我有一个游戏,从别人那边复制过来的时候,就是带毒的。文件型病毒,文件体被加了密,还给搬到了隐藏扇区。不过游戏本身挺有意思,The Incridible Machine,第一代。于是我每次就把 BIOS 里的硬盘给 Disable 掉,然后再玩。慢虽然慢点,但只要记得玩过之后重启,就不会有什么问题。

Chapter Two

尽管兜里没什么钱,但 Internet 时代还是到来了。
网管差点把我从网吧赶了出去。因为他终于「逮到」我在用 SuperRabbit。他已经连续好几天都把时间花在恢复系统设置上了。我想日后 硬盘还原卡 卖得这么好,其中应该有我一份功劳。
「机器狗」真不是我放的。

Chapter Three

CIH 爆发了。
买了刷 ROM 机器的人都发了笔小财。
其他人:哇,没想到病毒也可以这么凶残!
我倒是没什么感觉,因为我那个时候还在用着一块 80486。

Chapter Four

IDT-C6 的发热好低,主频超到 100MHz 也不用风扇。
哇,有好多同学都安装了 冰河 哎!
咦,这位同学在玩美少女梦工场 3 呢。我只有 2。
「同学你好,你的游戏可以也 copy 给我一份吗?哎哎,不要关机!……」

Chapter Five

不知道从什么时候开始……
几乎所有的 IT 公司,都以在你的 IE 上装一个插件或工具栏为荣。
几乎所有的安装包,都会附带一两种「小东东」。
刚开始我还真没太放在心上,甚至对某些还持欢迎态度。然后,我的机器越来越慢了。
做毕业设计时,我接触到了 ActiveX。

Chapter Six

工作了。从 K6-2 一下子跳到 Pentium 4 的感觉真好。
「见鬼,这浏览器上什么时候多了这么些鬼东西?上网助手?中文实名?卸掉!就是它搞得我们的 OCX 不能用了。」
「你要记得安装 Windows 2000 SP4 补丁,不然我们的程序用不了。」
对了,有个新软件叫 VMware,真好玩!

Chapter Seven

我开始小心翼翼地上网浏览,对于弹出的 OCX 安装提示统统点「否」,后面视情况而定。因为我知道一旦安装了之后别人能做些什么事情。然而,有些人我是注定帮不了他们:
我:「你的 IE 上装这么多工具栏干什么?」
A:「不然我怎么上网?」
我:「Google 的网址是三达不溜点……」
A:「别说了我记不住。」
愿上帝保佑他们,阿门。

Chapter Eight

换工作了,当小头头了,总算有双核电脑用了。VMware 可以有自己单独的 CPU 了。
真不敢相信,以前在一台 768MB 的 P4 笔记本上跑了一个 Oracle8i + Lotus Domino R5 + 两个 Resin + Word + 若干 IE,去招标现场做演示的时候,机器到底是怎么撑下来的。

借着新工作接触到了 IceSword。妖魔鬼怪你们都现出原形罢!
测试人员:不好了,机房又爆发「震荡波」了。

Chapter Nine

我开始仔仔细细地打补丁,开启 Windows Update,总是保持自动更新。
而很多同事都是直接关掉了事:「工作到一半老是跳出来叫我更新,太烦!」
愿上帝保佑他们,阿门。

Chapter Ten

我发现同事们总算愿意给机器打补丁了。
同事:这个东西叫 360,打补丁蛮快的。
我:我也来试试。咦,这个 KB360018 怎么这么奇怪?

Chapter Eleven

不知道从什么时候开始,软件安装开始必须得小心了。
因为一不小心你就会安装上好几个不请自来的软件,电脑上会变成软件博物馆。送来给我「修一下」的电脑无一不是如此。连我自己有时候不小心也会中招。
有人建议我把家里电脑换成 Linux,不过我觉得对父母吩咐到位了,一般还是没事。毕竟我有 TeamViewer

Chapter Twelve

不知道从什么时候开始,国产软件慢慢地不能用了。
其实不是不能用,是不敢用了。因为开始流行一种东西叫做「全家桶」。腾讯、百度、迅雷、阿里、360、金山……。所有的这些曾经为我服务过的软件,仿佛都得了癌症。你不知道他们在背后干些什么,但是你的电脑的确越来越慢,行为越来越不正常了,而且你的 hosts 总是失效得很快。于是干脆就不用了。
不用了,然后也并没有什么事情发生。并没有像有的人以为的那样会社会大乱,民不聊生。人民照样活得很好。

Chapter Thirteen

一夜之间,我的密码就不再是「我」的密码了。
许多年前,我自己写的同学录,为了避免被 SQL injection,就把服务端存储的密码改成了 MD5 Hash。
后来,知道了世界上有种东西叫做「彩虹表」,于是我学会了 salt
再后来,我看到了 王小云教授 的论文,于是我 Hash 算法至少会是 SHA256。
然而,这么多年过去了,这帮狗日的居然还在服务器上存 明文密码。谁要硬说这里面没有阴谋,我只能说你的心挺大。

Chapter Fourteen

没想到,我的 QQ 也被盗了!
我已经很多年没用过 QQ 了。但是因为我太太的手机上有游戏用我的 QQ 账号登录着,所以我觉得企鹅还不敢回收我的账号。
突然间它就变了一个名字,列表里的好友也变成了一堆海南人。我还能再登进去,密码并没有被改。所以我一直到现在也没想通是怎么回事。
我把个人状态改成了「明文密码好」,然后就把这个 QQ 号扔那里了,就像它从未存在过。

Chapter Fifteen

那个周末,当我还在外地顶着紫外线用流量上网的时候,全世界有许多人已经  了。
贼要的是 Bitcoin
当第二天 IT 问我笔记本装没装补丁的时候,我微笑着告诉他「Linux」。
不过我马上又想起来 Heartbleed 的事情我还没处理,于是我又有点笑不出来了。
我也想要别人的 Bitcoin。

2017-06-13

银联钱包你真垃圾,我一点都不欢迎你

5 月 30 日一下飞机,我就在虹桥 T2 的廊桥里面看到了银联云闪付 62 折的大幅营销海报。「去看看有没有便宜可占」,当时我这样对太太说。然后我们就各忙各的,这事几乎给忘了。

昨天太太跟我说,超市里面银联云闪付满 79 减 30,所以她在自己的 iPhone6+ 上装了个银联钱包。我想起了在机场看到的东西,就也去 AppStore 上搜了一下。有两个东西,一个叫「银联钱包」,一个叫「云闪付」,都是「中国银联」出品的,评分还真是一样低。我有点纳闷,好吧两个都下了,反正 AppStore 上至少没木马。

安装好之后把玩了一会儿,「云闪付」一来就叫我登录,而「银联钱包」至少让我看到里面有些什么功能了,里面也有「云闪付」的功能,那么好,「云闪付」你滚蛋吧。

其实我知道 Apple Pay 本来就是所谓的「银联云闪付」,我只是想知道银联这次在玩什么花样。结合新闻我有点弄明白了:大概闪付需要芯片,很多地方只肯买扫描枪,所以银联这次也搞了个跟支付宝有点像的扫二维码支付。这就必须要 App 支持,光靠 Apple Pay 还不行。

好吧,我觉得至少比起阿里粑粑这种流氓公司而言,银联我还可以尝试一下。于是我准备注册了……

我真的没想把本文写成吐槽文。不过正式开始用的第一眼我就被雷到了。拜托!中国银联!这是 iOS,是哪个老师教你「自己开发密码键盘会更安全」的?你说出来我们来轮他。

先得注册。我在界面上看到了可以用「手机号」、「邮箱」或「用户名」来注册。我并不是一个暴露狂,所以我准备以用户名来注册。点开 App 上的注册,发现只能用手机号注册。我不死心,换到 PC 上在 Web 下注册。一开始 Web 页面看起来也只能用手机号注册,不过当我 F12 之后,就发现还有一个 DIV 被 display:none 了。
呵呵,我心想:「就这也能难住老子?」
看来还是绕不开。

然后还有这个令我一看到就恶心得头皮发麻的「点此安装」。
我用的是 Chrome,要我换用 Edge 甚至是 IE 我都可以接受。不过要我安装 OCX 那就太过分了,。有的 Web 页面如果用手机浏览器打开,就不会提示安装控件了。我抱着姑且试一试的态度,用手机试了一下:
Chrome 是这样,Safari 也是这样。
好吧,反正就是必须得要手机号对吧。那我还是回手机 App 上去注册吧。

在 App 上输入手机号码,通过短信发了验证码给我,验证通过了,接下来让我输入密码。这明显的大爷作风嘛。我很想问银联你是不是并不在乎有没有用户来注册?反正老子是国企,用户多一个少一个无所谓?你们知道像美团之类的 App 在这一步是怎么做的吗?
我又要吐槽了。最低 6 位虽然太少,但我可以理解。最多不能超过 16 位是个什么意思?你们如果后台数据库里面保存的真的是密码的 Hash 值而不是原文,你管我密码最大有多长?你们知道 Twitter 允许的密码最大有多长吗?你们可以自己去试一下。
明文密码!我一直不厌其烦地在 Twitter 和 Google+ 上强调这个事情:只要没有特别的理由就限制密码的最大长度,那后台保存明文密码的可能性就一下子变得高了起来。不要跟我讲什么 16 位现在还足够安全。关键是撞库!撞库!撞库!
我一直是坚持「为每个服务使用独立随机密码」这个原则的。我宁愿忘掉密码,宁愿冒本地密码本被人搞走的风险,我也不会把心放在 Server 管理人员身上。原因很简单:做过这行你就知道了。
所以,我开启了密码生成器,去掉「符号」的勾,选择了长度为「16」,然后得到了一串随机密码。知道我前面为什么吐槽自己做的密码键盘吗?你既然决定了自己做密码输入控件,就很可能不会支持复制粘贴。
在我比较辛苦地输入完密码之后,出来一个这个。
估计我以前在什么时候注册过银联的账号吧。有句妈卖批我不知当讲不当讲?!我输入手机号码的时候你不告诉我,我输入短信验证码的时候你不告诉我,我输入密码的时候你不告诉我,等我把这些都搞完了你就告诉我这个?!
抱着一丝希望,我点下了「是我的,立即登录」按钮,App 跳回到了最开始的登录界面,输入我刚才生成的随机密码(是的我又辛苦了一遍),说我密码错。这次我真的骂人了。
好吧,看来我只能选择「忘记密码」了。
在又通过短信验证了一遍手机号之后,给我看的是这个界面。
我曾经在 Blog 中讲过,「密保问题」并不是一个好的设计。不幸的是,银联这里选的是所有密保问题中最糟糕的那几种之一。如果真的用家人生日做密保问题的答案,安全性极其脆弱,有等于无。如果用别的答案,又极容易忘记。
偏偏这里不回答还不行,又没有提供「忘记答案」的选项,实际上是把这条路给堵死了。其实手机短信验证的安全级别明显比密保问题要高。取信低安全等级的验证结果,而忽视高安全等级的验证结果,最后只能请求人工服务,这是极差的用户体验,也是极蠢的产品设计。

如果我记不起这个以前注册的账号的密码,我可能就只能打电话给银联了,而且可能这帮官腔佬最后还不肯替我解决。不过很幸运,我用一个旧密码最后成功登录了进去——看来我注册的时代还不太「古老」。
然后我又被雷到了。登录成功之后,App 给我看了这个:
是的,没有「跳过」或者「稍后设置」的选择,这一步是必须的。
设置手势密码之后才可以设置 TouchID。好在根据太太的经验,登录进去了之后是可以把手势密码功能给关掉。不过问题又来了,要进入「安全设置」你必须要:
到这一步,我彻底放弃了。
极其差劲的技术运用,极其糟糕的用户体验。难怪 AppStore 上绝大多数评价都只给了一星,而近期的五星好评全像是刷出来的。我估计如果 AppStore 不是规定最低是一星,很多人连这一颗星都不想给。
说实话,我不相信这种程度的技术能够保障我的资金和信息安全,给我天天打 62 折我也不敢用。算了。卸载。再见!


2017-05-31

淘宝天猫都碰不得啊

前言
六一儿童节快到了,给儿子买点什么礼物好呢?虽然前不久刚买了一架 UH-60 和一把 Glock17,但我还是想买点对儿童稍微有点教育意义的东西。思来想去,觉得地球仪不错。正好暑假也准备带家人出国去转转。若是连自己去了哪里却一点概念都没有,那岂不是很遗憾?

平时工作也算得上忙,所以我就偷懒打算在网上购物。碰巧近来网购的体验还不错,正所谓好了伤疤忘了痛,我又开始在淘宝上逛了。

其实以前我是很吃过几次亏的,所以也曾经痛下决心告诉自己大部分东西都不能在淘宝上买了。但前不久那次买玩具的感受实在是不错:我擦!居然有金属的 Dragunov?这次一翻地球仪,我更是震惊了:我擦!居然还有磁悬浮的?!

所以说啊,人必须时刻保持冷静,才能抵挡诱惑。头脑发热之下,我真的就下单买了个所谓的「磁悬浮地球仪」。带着残存的一丝丝理智,我选了一家天猫店下单。我觉得吧,C2C 的淘宝不靠谱,B2C 的天猫大概好一点,至少出了问题比较好解决。事后证明,我这想法也对也不对。

糟糕
等待收货的过程还是蛮引人遐想的。然而收到货一拆包装,首先心里凉了一半:包装倒没有什么特别的问题,不过产品外包装上连半个汉字都没见到,没有品名,没有厂家名称,没有商标,连个 LOGO 都没有。好在我是懂英文,加上有个图片,还能看出来没发错货。
打开盒子,里面除了地球仪和一些填充物,就只有一张非常简单的「说明书」。合格证、保修卡,什么都没有。这它妈的不就是传说中的“三无产品”吗?
好吧,网购的东西,要求别那么高,毕竟地球仪才是主要的。我对它有着不小的期待,因此心里倒也没怎么在意那些「细枝末节」的事情。看了看说明书,通上电源,开始折腾。折腾着折腾着,汗就下来了。
要完成所谓的「悬浮」,好困难啊!

虽然买之前看过一些评论,算是心里有所准备,但还是没想到这么麻烦。送的那个什么「悬浮棒」根本就不好使。还是有个评论里面说用中性笔的办法比较管用。好不容易能够比较顺利地「悬浮」上了,然而只要有一点点扰动,平衡立马就被打破,通常的结局就是地球仪被「铛」的一声吸到顶上。而且就算是很小心地退开,一般也坚持不了几分钟。我发现这大概跟加工精度有关系,因为顶上的磁体是斜的,而我无法去校正。
并且,产品说明里面提到的自动旋转,似乎也无法达到。球体也就是左右来回转,每次角度也就是几十度。我定睛看着它,发现角度在越来越大,正当我满心希望它最终能转一个整圈的时候。「铛」,它又被吸到顶上去了。

这它妈让人怎么用?我不可能每天都花一两个小时的时间在折腾这东西上。睡觉时还不能开着,不然保管神经衰弱。要是只能摆着或吸到顶上,那几十块的普通地球仪岂不是更好?我干嘛买个这么贵的银样蜡枪头?
不行,退货!

退货
在淘宝上买东西,顺利的话一切都好。然而一旦买到糟糕的玩意儿需要退货,那麻烦就来了。我以前的糟糕的购物经历无一不源自于此,这次当然也不例外。当我以「质量问题」为原因要求退货时,卖家拒绝了。我对此也并不意外——反正最糟糕也就是哪样,总不可能直接投降吧。

卖家拒绝的理由看起来很好笑——产品有正规工厂专利进货发票。这话好眼熟啊!对了,我以前在某个论坛上看卖家们交流心得时提到过,如果被投诉三无产品,就这样应对,只要你拿得出发票来,淘宝小二可不管它是不是真的……。呵呵,这样的发票当然可以买。有些买的发票连税务局那边都能过,何况淘宝。淘宝的处理方式很简单,C 说你卖的是假货,你 B 有发票吗?噢,有,那 C 你去证明 B 卖的是假货……
我去你妈的。

这个时候我看到了一个「极速维权」的按钮。我点了一下,叫我提交证明。
我能怎么证明呢?我只能把上面那两张图片传了上去。产品不能正常使用,我能怎么证明?上传一段小电影吗?搞笑。我都能想得到接下来的过程,无非就是叫 C 去找质监局,出具鉴定证明。普通消费者常常到了这一环节就望而却步了。较真的可能会去工商局投诉,然而一般也是了无下文。

要说起来,这个环节是很给「天猫」加分的地方。我刚上传了照片不久,天猫说因为我是「信誉良好」的顾客,所以给我先行赔付了。的确也是马上就是进入到退货发物流的环节了。听明白了吧——东西真不真我不表态,但是我相信你!
很多时候,事情就这么摆平了。你要认真按「假一罚 N」地去索赔,那可就没这么容易了。职业打假人毕竟是少数。这一招就叫做「分化瓦解」、「团结大多数,孤立一小撮」。

给东西重新装箱的时候,我又留意了一下这东西的做工,才发现其实粗糙得很,根本不值 169 元这个价钱。所谓专利号,搜了一下是宁波一家公司的,跟淘宝商家八竿子打不着。如果光看成本,20 元都不一定有。尽管到淘宝上买东西没一个不是想贪便宜,但你一定要相信,卖家不会让你真正占到便宜的。

运费
我记得,我上次在 Blog 上吐槽淘宝的事情,最后问题也是出在退货的运费上。这次又是「也不例外」。可见,如果那里的确有一个坑,你再走几次也还是会掉进去的。

天猫很「贴心」地提供了「上门取件」服务,还可以约时间。说实话,当时我真的有点小「感动」,几乎就要拍手叫好了。

取件的人来的时候,我在上班,是我太太处理的。取完件我立马就收到了退款。然而收快递的人跟我太太说,运费本来是 7 元,但是要收 11 元,因为「你的东西超宽了」。
好吧。我心想,反正质量问题的退货运费是应该由卖家承担的。所以我也就没有坚持太多。收快递的人(我不想用「快递员」一词称呼他,因为我对他有意见,我觉得他不配)叫我支付宝直接付掉,我打开手机的淘宝 App,的确有付款按钮。我点开看,7 元。

怎么回事?也许是对方还没提交新的价格。我就又做了一些手头的工作。一个小时以后再去看,还是 7 元。
7 元就 7 元,我心想,也许系统不觉得我的东西「超宽」。付完款没多久,太太说快递员打电话来,叫补 4 元运费,给了支付宝地址。
妈的,现在想起来补运费了?

结束
讲个笑话:太监下面有什么?
什么都没有了。

质量问题导致的退货,运费不是由卖家承担吗?如何承担?要我「垫付」已经是足够糟糕的体验了,然而现在是要我去找卖家聊IM来「讨」吗?
我最憎恨的就是这种「干点什么都要 IM」的事情。而前几次还算「愉快」的购物体验,也正因为我「不用到 IM 上做任何事情」。

好吧,既然天猫觉得这个事情「到此为止」了,那我对你淘宝 / 天猫也就「到此为止」吧!
其实总结下来,写了这么多,无非是想让自己记清楚这些个教训。有钱留着去国外花,别它妈犯贱去交智商税。

2017-05-25

将 C++11 新特性用于代码优化

关于 C++11 的科普,在这里就不详细进行了,可以参考 维基百科 页面。即使是中文页面,我认为写得足够详细和系统了。

总之,C++11 对原始的 C/C++ 作出了在我看来是不算小的改动。有一些概念,放在以前的时代是绝对真理,在 C++11 推出之后,可能需要重新了解一下了。VS2013 对 C++11 的支持并不算「完美」,不过大部分「有用」的特性还是到位了。这里就以它为例,来谈谈如何把 C++11 的新特性应用到你的软件开发工作中来提升性能和开发效率。

本文提到的 C++11 的这些新特性,我大致把它们分为两类:一类是可以直接提升代码的性能表现的,我列在「性能优化」部分;另一类虽然不能直接提升代码的性能,但可以提升开发效率,便于更快地开发出可维护性更好的代码,我列在「非性能优化部分」。

另外,受作者水平所限,本文并不是对 C++11 在这些方面的完整的参考内容,仅仅作为一个引导来阅读吧。


性能优化部分

右值引用和 move 语义
C++11 引入了右值引用,支持了 move 语义。在我看来,这个变化的意义可能是 C++11 里面最大的一个。右值引用和 move 语义是什么,这里不展开。通俗一点地讲,这个特性使得程序员可以在必要的时候自行决定到底是深拷贝还是浅拷贝。对于大量的数据「搬运」操作,可以节省下不少时间。对于性能优化来说,意义重大。

其实就算没有右值引用,在 C++11 之前的时代也可以做类似的优化。C++ 程序员只要对于自己的资源管理类显式地提供深 / 浅拷贝版本的函数即可。不过这样一来代码工作量会比较大,程序会变得比较复杂,并且始终不是一个规范。现在这一切都不是问题了。

对于 STL 自己的类/容器,VS2013 已经做了足够的优化。例如,你可以通过:
string strA = std::move(strB);
来把 strB 的字符串动态内存部分直接给到 strA,速度比简单的赋值要快不少。当然,strB 就不再具有有意义的值了(这里例子中会变成空字符串)。当你 push_back 或 insert 一个 string 到容器里面的时候,如果 string 其实是一个临时变量,那么用 move 语义你也可以得到相当明显的性能提升。

如果例子里面 string 换成一个 map<string, string>,那么提升会更明显。总之,内存的分配和释放,以及 memcpy 操作统统被避免了。所以,理论上需要传递的东西越多,你得到的性能提升就会越显著。

就地部署(emplace)
C++11 对于常见的 STL 容器,都提供了一种能提升性能的数据置入方法,称之为「就地部署」。通过用就地部署取代原来的 push_back 或 insert 之类的操作,不再需要先构造再传递,而是由容器直接调用目标对象的构造函数来完成数据填充。

在某些情况下(T 提供了对应的构造函数时),这样可以避免一次拷贝构造的开销。而最差的情况(T 没有提供对应的构造函数),也最多不过就是与 push_back 和 insert 效果一模一样而已。所以我建议所有能用上就地部署的地方,都统统用上,无需太多考虑。

并且,就地部署与 move 语义相互并不冲突,而且是互有补充。move 语义解决深拷贝慢的问题,就地部署试图减少哪怕是浅拷贝的执行次数。两者配合起来效果更加完美。

散列表
在 C++11 里,不再需要通过第三方库来引入散列表(或者叫哈希表)了。STL 正式支持了四种散列表的实现,全部都冠以「unordered_」的前缀,以便与一些第三方实现相区别。

对于大多数用 map / set 实现的代码,只要简单替换容器就可以得到性能上的提升。map / set 基于红黑树(自平衡二叉树),时间复杂度至少是 log(N)。散列表版本的 map / set 提供常数级的时间复杂度,随着数据量的增大,无论是写入还是读取的性能都超过了红黑树版本的 map / set。

我个人的测试结论是:同是 set<string>,即使是小数据量,散列表版读取代价也只是红黑树版的约 60%;小数据量下,红黑树版写入略快,但在容器内数据量达到「万」级别的时候,散列表的写入速度也开始超越红黑树版(此为 Release 版测试结论,Debug 版在「百」级别即发生超越现象)。

所以我认为,只有在数据量很小,并且写入与读取的概率大致相当时,使用红黑树版 map / set 才在性能上可能有明显收益。其余情况,都建议采用散列表版本 map / set。当然,如果 T 是自定义类,并且你不愿意为它写散列函数,那就算了。


非性能优化部分

完美转发
C++11 中所谓「完美转发」的特性,其实是配合右值引用来使用的。如果为了支持右值引用,而不得不让自己的代码量变大一倍,那有些人可能就要望而却步了。完美转发其实是借用了模板技术,使得你可以只写一份代码,就可以兼顾(常量)左值引用与右值引用的情况。工作量更少,代码更简洁,出错的概率也就更低。

不过,采用模板技术的缺点就是:编译期展开。这一方面降低了编译器的效率,另一方面会导致头文件的包含关系变得不太容易整理。除此之外,还有一种我称之为「不完美转发」的替代解决方案,本质上是在性能上作出一定程度上还算可以接受牺牲,来换取代码简洁性,取得一个还算 OK 的平衡。我会另外写一篇 Blog 来介绍一下它。

类型推导
「类型推导」也就是所谓的 auto 类型。这个东西使用起来基本没有门槛。很多人可能最开始接触 C++11 就是通过它了。

这个的确是一个好东西,用来写 STL 的 iterator 类型再合适不过了。因为我们本来也不怎么关心 iterator 的具体类型。不过,仍然不建议滥用。如果到处都是 auto,阅读你代码的人会经常性地需要回顾才能知道一个变量的类型,特别是在你没有用匈牙利命名法的时候更是如此。

所以,我的建议是:当你觉得一个变量的类型写起来很麻烦,而你其实并不关心它的时候,放心地用 auto。并且,auto 变量的作用域不要太大,if / for / while 循环内的局部变量用它是最合适的。

基于范围的 for 循环
很多语言早就可以这样写了。而 C++11 现在也可以这样写了:
for (auto& stk : stocklist)
相比起:
for (auto pIter = stocklist.begin(); pIter != stocklist.end(); ++pIter)
孰优孰劣一目了然。何况后者通常还需要跟一句:
auto stk = (*pIter);
不过,如果是一个 map,你可能经常要取 pIter->first / second 之类。或者你打算在循环里面对 pIter 做 erase 操作,那还是用传统方式比较好。

空指针
用 nullptr 取代 NULL。我觉得最大的好处就是 nullptr 的颜色没有 NULL 扎眼。不过,由于 NULL 也表示 0,有的时候也表示无效句柄。我觉得对于所有指针类型的 NULL,置换成 nullptr 可能会对阅读代码有一定帮助。

角括号
C++11 的编译器现在可以识别 >> 到底是两个模板类的嵌套,还是 >> 运算符。因此写代码的时候就不特意空上一格,写多层模板类嵌套的时候就更美观一点。

不过,多层模板类嵌套,本来就不可能「美观」到哪里去。起码我是不建议太多此类的代码实践的。

初始化列表
vector 可用这样的方式来进行初始化:
vector<int> vecX = { 1, 2, 3, 4 };
的确是比以前省事了。也就是说,C-Style 数组的存在意义又少了一层。

统一初始化
struct 可以被这样初始化:
struct C
{
    int a;
    int b;
    int c;
};
C c{1, 2, 3};
class 的 public 成员也可以。
在某些喜欢使用各种结构体的代码中,这个特性可以让你少写一大堆构造函数。

通用智能指针
std::shared_ptr<T>,强在可以指向任意对象,缺点也由此而生:由于引用计数保存在 shared_ptr 中,因此对智能指针的赋值操作是线程不安全的。这个问题,有一篇Blog论述,我觉得写得不错,就直接引用不细讲了。从原理和测试数据来看,我认为这篇 Blog 是靠谱的。

所以,虽然 shared_ptr 很强大,但使用场合需要注意:单线程随便用。多线程下,赋值过程要注意。单对单没啥问题,最好不要出现左值右值交叉的情况(一个线程在 A = B,另一个线程在 B = C)。若因业务需求无法避免的话,要考虑当作临界资源加锁保护。实在不行,就写一个专用智能指针,把引用计数放在 T 里面,加锁保护,就不会有问题了。

正则表达式
与散列表类似,不再需要第三方实现,现在 C++11 也直接支持正则表达式了。我以前要找一个 Unicode 支持得好的 Regex 库真的是苦水一堆,现在有了官方支持真的是太好了。

2017-05-23

招行网银在 Win10 下糟糕的支付体验

我这人不用 QQ,不用微信,微博借王维林也成功销号。然而,支付宝因为早年上淘宝购过物,搞过那个什么实名认证,我评估后认为不用比用下去可能更不安全,所以账号勉强留了下来。不过我也给自己定下了两条规矩:
  1. 不绑定银行卡的快捷支付。
  2. 不在里面保留超过 200 元人民币的余额(含余额宝)。
这两条规矩,使得我的支付宝平时基本上当作零钱包来使用,正式的支付只用在 PC + 网银上。这样,即使失窃,损失也很有限。再说,花钱方便是方便什么?方便你「败家」而已嘛。

因为我自己定的这些规矩违反某些方面的利益,所以经常给我形成一些耐性上的挑战。昨天我就又遇到了一次,来自招行网银 + Win10。
没有足够的余额,又不开快捷支付的话,在支付宝上支付一笔淘宝订单就只能通过网银了。我以前一直用招行网银,虽然它对 Chrome 和 x64 很不友好,但我还是耐着性子开 x86 IE 来在这种j「特殊场合」将就一下。反正也就是最后一步嘛,登录 Alipay 集中支付一下就好。但昨天在 Win10 机器上我又发现了新的问题。

Edge 不支持
Edge 登录支付宝是挺顺利的,但招行网银的页面一打开,直接说这是「使用陈旧技术的页面」,估计是因为 ActiveX。要想跨 Browser 提交表单估计还是做不到的,所以最后还是只有在 Win10 下打开 Internet Explorer。

无管理员权限登录会失败
IE 下几经折腾,可以打开招行的网银专业版了。然而插入 UKey 之后一登录就跳出 Dialog 让我输 Key 的密码。在确认不是钓鱼软件的情况下,连输四次后,专业版登录界面弹出提示说证书签名有问题云云。
我估计这问题就是因为权限不到位,果然给了管理员权限后就能正常登录了。然而专业版登录界面是从 IE 页面上 Call 出来的,这就意味着我得给 IE 管理员权限才行。这样的话风险就有点高,还好可以手动做单次授权。

以前我一直觉得,金融行业在 IT 技术应用上普遍保守,而在国内放眼看来,招行在技术方面还算是相对进取的。现在看来情况已经反转了。x64 都普及多久了?IE 份额离开绝对优势地位都多久了?Win10 正式版都上市多久了?到现在还在用着 ActiveX。招行里面搞技术的人这些年都不知道在干啥。曾经引以为傲的服务水平也许还没下滑,但也绝对不算突出了。我是不是该换一家主力银行了?

2017-05-22

VMware + Ubuntu 声卡失效事件

在公司和在家里,都用 VMware 各自安装了一台 Ubuntu 14.04 LTS 来玩。在家里的一台用得没有什么问题。在公司的那一台,周末打算加班的时候装个网易云音乐来听歌的时候,发现没有声音,才注意到 VMware 上有一条报错提示:
使用的设备标识号已超出本地系统范围。 声音将中断。
公司电脑上没有接音箱,所以以前曾经禁用过宿主机的 Windows Audio 服务。我以为是这个原因,去看了一下,Windows Audio 服务现在是启用中。把宿主机重启过,故障依旧。于是循例开始 Google。

Google 上搜到的中文内容,主要分为两派。一派说把 pulseaudio 卸载了就好了。我半信半疑地 apt-get remove pulseaudio 之后,嘿,还真的可以播放出声音了。不过更大的问题来了:系统设置丢了好多图标。回想起 apt-get 提醒我说要卸载掉的东西有一大堆,看来依赖于 pulseaudio 的东西不少。这条路应该不是什么正路。真是还好 VMware 有快照。

另一派说把宿主机上的立体声混音设备启用,故障就解决了。附和的人不少,看来有不少人都是这种办法解决的。具体页面有很多,随随便便就能搜到,我就不给出了。然而当我按照附带截图的操作指南去做的时候,问题又来了:我根本没有立体声混音设备。

这是怎么回事?我这人也不习惯卖关子。要说还是英文信息有用。英文页面上也有少数几个人抱怨遇到与我同样的问题(中文页面上我没有看到过)。最后还是 VMware 官方社区给出了有用的 解答

要简单解释一下的话,其实就是这么一回事:我这个宿主机上的 Win7 当初装起来之后,偷懒没有安装 Realtek 官方的声卡驱动程序,而是直接 Windows Update 安装了微软给出的驱动。估计微软的这个驱动是个阉割版,缺一些东西,装了之后虽然使用起来没什么问题,但像我这次遇到的什么立体声混音设备,大概就是被阉割掉的内容之一。所以 VMware 找不到指定的设备,于是就没法让 Ubuntu 中的声音设备正常工作了。

总之,按照 VMware 官方解答的指引,我去 Realtek 官网上下载并安装了声卡驱动,现在 VMware 里面的 Ubuntu 可以欢快地播放音乐了。

2017-05-21

正确地获取 Windows 的版本号

以前,想要获取 Windows 的版本号很简单,有个 Win32 API 函数名字叫做 GetVersion,望文生义,接下来要做的事情就是去 MSDN 上查下用法就可以了。

现在,GetVersion 会被报告成「过期函数」了。也许还能用,但(据 MSDN 说)起码在 Win10 上是别指望得到预期的结果了。道理也很简单,Win10 都搞滚动升级了,版本号规则肯定也和之前不一样了,你还指望这么老的函数能兼容么?

别痴心妄想了,GetVersionEx 也一样过期。那么,眼下有什么好办法吗?

一般来说,拿 Windows 版本号可能有两种用途:

  • 我想看看你 Windows 版本达到我要求没。
  • 我就是想知道你 Windows 版本号是多少。

对于前者,微软现在在 MSDN 上是这样推荐的:它做了一组 Version Helper functions,你如果想知道当前 Windows 的版本是不是某个特定的发行版,调这组函数就可以。我们来看看这组函数中三个典型:

  • IsWindowsXPOrGreater
  • IsWindowsXPSP3OrGreater
  • IsWindowsServer

不需要更多说明,我们从名字中就可以看出,这组函数可以用于判断 Windows 的大版本,Service Pack 的版本(结合大版本),以及能知道是不是服务器版操作系统。通常情况下,这些函数大概是够了。

但是,有的时候我们并不关心版本号高低,我们只是想要一个版本号(例如记录日志时)而已。微软对此的建议是:用 GetFileVersionInfo 去获取一个系统 DLL(例如 Kernel32.dll)的文件版本号(原文看 这里)。

相关的代码虽然能找到,MSDN 上也有官方例子(有点小 Bug),但比起一行 GetVersion 来代码量实在是不能算很少。由此可见,处理「过期函数」真的没有想象中那么容易。最后我还是提供一下我从项目代码中挖出来的一个实现吧。别照抄,如果你不想引入 STL 的话:

#include <windows.h>
#include <Strsafe.h>

#pragma comment(lib, "Version.lib")

// 获取文件版本
std::wstring GetFileVersionString(const std::wstring& strFilePath, bool bStrVer = false) {
    DWORD dwVerInfoSize = GetFileVersionInfoSize(strFilePath.c_str(), nullptr);
    if (dwVerInfoSize) {
        std::vector<BYTE> vecVerData(dwVerInfoSize);
        if (GetFileVersionInfo(strFilePath.c_str(), NULL, dwVerInfoSize, &vecVerData[0])) {
            LPCVOID pBlock = &vecVerData[0];

            UINT cbTranslate;
            TCHAR SubBlock[MAX_PATH];
            struct LANGANDCODEPAGE {
                WORD wLanguage;
                WORD wCodePage;
            } *lpTranslate;

            // 阅读语言和代码页列表
            VerQueryValue(pBlock,
                L"\\VarFileInfo\\Translation",
                (LPVOID*)&lpTranslate,
                &cbTranslate);

            if (bStrVer && lpTranslate) {
                // 读取第一种语言和代码页的文件版本
                for (size_t i = 0; i < (cbTranslate / sizeof(struct LANGANDCODEPAGE)); ++i) {
                    StringCchPrintf(SubBlock, sizeof(SubBlock) / sizeof(TCHAR),
                        L"\\StringFileInfo\\%04x%04x\\ProductVersion",
                        lpTranslate[i].wLanguage,
                        lpTranslate[i].wCodePage);

                    LPVOID lpBuffer = nullptr;
                    UINT dwBytes;
                    if (VerQueryValue(pBlock, SubBlock, &lpBuffer, &dwBytes) && lpBuffer && dwBytes > 0) {
                        std::wstring strVersion(reinterpret_cast<TCHAR*>(lpBuffer));
                        return strVersion;
                    }
                }
            }

            // 未找到任何字符串版本
            VS_FIXEDFILEINFO* lpffi = nullptr;
            UINT uLen = 0;
            // 注意:这里的第二个参数 "\" 是固定写法,表示查询根块
            if (VerQueryValue(pBlock, L"\\", (LPVOID*)&lpffi, &uLen) && lpffi && uLen >= sizeof(VS_FIXEDFILEINFO)) {
                std::wstringstream wos;
                wos << HIWORD(lpffi->dwFileVersionMS) << L"." << LOWORD(lpffi->dwFileVersionMS) << L"."
                    << HIWORD(lpffi->dwFileVersionLS) << L"." << LOWORD(lpffi->dwFileVersionLS);
                return wos.str();
            }
        }
    }
    return L"";
}

// 获取 OS 版本信息
std::wstring GetOSVersion(const std::wstring& strWinSysDir, bool bStrVer) {
    std::wstring strWinSysFilePath = strWinSysDir;
    if (!strWinSysFilePath.empty() && strWinSysFilePath.back() != L'\\') {
        strWinSysFilePath += L'\\';
    }
    return GetFileVersionString(strWinSysFilePath + L"Kernel32.dll", bStrVer);
}

2017-05-20

SDL 检查不报错事件调查报告

Visual Studio 2013 VC 项目默认是启用 SDL 检查的。通常而言,这会使得一些「过期」函数在编译时被报告 Error。比如 strcpy 和 inet_addr 之类都会遇到这个问题。

理论上讲,这些过期函数的确不安全,或者说容易被不安全地调用。微软也很「贴心」地在编译器的报错信息中给出了解决方案,比如用 strcpy_s 和 InetPton 来替换,都不用你去搜解决办法了。所以按照我的习惯,一般是就地解决掉这些问题再往下走。

不过呢,可能有的人性子比较急,也有的时候是从旧项目移植,想快点编译完先跑一下看看效果。改代码毕竟要时间,从 strcpy 改成 strcpy_s 可能还好,而从 inet_addr 改到 InetPton 就真的没有想象中那么轻松。所以编译器也给出了另一种建议,你可以设上几个 Macro,SDL 也是可以被忽略的。

到此为止都还比较和谐,大家都是有商有量地做事情。然而当我腾出时间准备把过期函数扫扫干净,把同事临时加的 Macro 去掉之后一编译,问题来了——SDL 这次怎么不报错了?

再三确认过 vcxproj 中已经没有了相关的 Macro,并且 SDL 的确是打开了。但这次编译就是不会报错,似乎对我面前的 strcpy 视而不见。为什么呢?

Google 上搜了一大圈也没有方向。最后还是被我排除法硬试出来的——平台工具集如果选了 v120_xp,那么 SDL 即使打开,有些过期函数也不会报错。是不是 SDL 就此失效,我不清楚,因为我没法去覆盖所有的过期函数。我估计,微软是这样想的:你既然打算让这个程序跑在过期的 OS 上,那函数过期不过期已经不重要了。

其实还有一个更重要的原因:strcpy_s 还好,但要是把 inet_addr 真的换成了 InetPton,你就会发现在 WinXP 下你的程序根本就跑不起来。实际上,WinXP 就不支持 InetPton。MSDN 上的信息表明,最低也要 Vista 才可以。

我们的程序暂时还不能抛弃 WinXP 用户,但若要完全不知道用了哪些过期函数我又心有不甘,于是我打算做一个 #if……#else……#endif 来解决这个过期 OS 兼容的问题。搜了一下,正确的姿势应该是:
#if (_WIN32_WINNT >= 0x602)
#else
#endif
最后我是在 Debug 版本上用了平台工具集 v120,在 Release 时还是用了 v120_xp。过期函数只会局限在以上 Macro 范围内,有限度地使用。

另外,WinXP 也不支持条件变量 CONDITION_VARIABLE,所以这个服役期超长的操作系统是真的应该淘汰了。我不得不说一句:WannaCry,干得好!

2017-05-19

世界都在发展,而我们在翻墙

Google I/O 2017,没有熬夜看,只是大致了解了一下主要内容。
一转眼,Google+ 上线已经快六年,这六年中,热点变了好多次。SNS 貌似早已过气,大家都在纷纷议论 Google+ 到底什么时候被砍掉。曾经的热点 Android 现在已经变成波澜不惊的东西。Glass 悄声无息已经很久。无人驾驶汽车现在好多家都在搞。Assistant,Allo,Google 现在重点关注的是人工智能了。
也许 SkyNet 真的会出现,也许 Terminator 很快就会到来。我倒不太在意「人类被消灭」这种事情。如果能够见证这一时刻,也是一件幸事。

然而!眼看着世界都在发展,眼看着人类朝着技术奇点越走越近,我们在干什么?我们还在努力翻着墙,网络翻墙,肉体翻墙,精神翻墙,就在下下个周日。

美团那个 HR,不是歧视,只是蠢

最近有个「美团招聘」事件在网上发酵。孤陋寡闻的我今天才知道。众说纷纭,我也来发表一下意见。

------------------------------------------分隔线------------------------------------------

这位据说来自美团的 HR 抛出了一个「五不要」的招聘原则,被人发在了网上:
  1. 不要简历丑的;
  2. 不要研究生博士生;
  3. 不要开大众的;
  4. 不要信中医的;
  5. 不要黄泛区及东北人士。
美团说已经把这个人辞退了。简单关注了一下,不少评论意见很简单:要不说这人说得有道理,要不说这人脑残搞歧视。互联网信息快餐时代,简单的意见的确比较容易表述,也比较容易挑口水,所以我觉得我还是写个 Blog 说一下自己的意见。

HR 的招聘原则,脑子能想,但不能说或写出来。一旦别人知道了,那就是「歧视」。我觉得这个没啥大问题,不过我不关注这个层面。如果原则正确合理,即使是写了出来,我也觉得没啥问题。但是这「五不要」到底有没有道理,我觉得一定要分开来一条一条地看。如果你要么全盘接受,要么全盘否定,那么有可能脑子不太好使,请去医院挂号。

不要简历丑的
「丑」这个词,很主观。所以这个 HR 这样写出来,脑子不会太好使。主观的形容词你只能自己用,给别人讲了也无意义。你心中的「丑」跟别人心中的「丑」肯定不是同一回事。
那么,这一条有没有道理呢?当然有道理。招聘的考察因素当然可以包括被招聘者的审美和美术功底,包括排版能力。就算不是相关岗位要求的技能,但「审美观」作为对一个人综合素质的要求是并没有什么问题的。这一条,道理上没问题,只是不能写出来,因为写了也没意义。

不要研究生博士生
首先这一条写得有点问题,如果写成「不要研究生及其以上学历者」或「只要本科(含)以下学历者」可能更合适。不过我也不想抠这种文字上的细节。单看这一条表述的意思,一点问题也没有。招聘条件中对于学历的要求,这个能有什么问题?也许其他人觉得 HR 是歧视高学历者,但也可能是岗位不需要,或简单的「雇不起」。你要是看出来里面有「歧视」,那其实只说明你心里才有「歧视」。

不要开大众的
我初看到这一条时感觉简直是「这人神经病」。「开大众的」怎么着你了?撞你了还是蹭你车了?上网一搜才知道所谓「神车」是啥意思。
那么我们现在知道 HR 是什么意思了。不过,这一条问题很大。HR 不想要具备某种价值观的人,这个很容易理解。但是开个大众车就一定具有某种价值观吗?可以举出反例的情况太多了。后面的分析会告诉我们,这个 HR 一定不是一个好的理科生,他的逻辑学连基础都没有。

不要信中医的
这一条没有问题。信中医的会有什么价值观,我们还是很清楚的。HR 不想要有这种价值观的人,那是他的自由,一点问题也没有。

不要黄泛区及东北人士
看到这一条我又糊涂了。什么是「黄泛区」?黄皮肤泛滥?三藩还是雪梨?查了一下才知道说的是河南那片儿。不过总之「地图炮」仨字跑不掉。
这一题也跟第三条一样,问题很大。你一个 HR 招个白领而已,要考察的无非主要就是能力和性格。这两样哪个跟地区有必然的关系?

------------------------------------------分隔线------------------------------------------

好了,现在我们来看看这位 HR 犯了什么毛病?
他的问题不是「歧视」。他是脑残,是逻辑没学好。他不知道什么是充分条件,什么是必要条件。我估计他中学时平面几何的分数一定很低。

招人的时候,你若是 HR,一定会提一堆的条件。这些条件里面,有一些是你要求应聘者必须满足的,不具备的话就别来了,来了也没用。这种叫做「必要条件」:若要应聘我司,必须如何如何。通常这类条件都会是「白名单」形式,符合这类条件,你只是有了资格,但并不保证一定会被聘用,也不保证不会被其它条件给刷掉。
还有一类条件,如果你符合了,你就别来了,不会考虑你的。排除性的,也就是上面提到的「XX 不要」。这类条件,叫做「充分条件」:你只要如何如何,我就一定不要你。注意,这里一定是「黑名单」性质。除了骗子公司,没有 HR 会说「只要你符合 XX 条件,我就一定要你」。全世界都没有,不然这家公司早倒闭了。

黑名单有效的前提,是该条件可以充分覆盖 HR 希望避免的区域。换句话说,你排除掉的,一定是你不想要的。你的条件和你要的结果,中间要有逻辑关联性。举个例子:你不喜欢染头发的员工,你就列一条「不得染发」。你要列一条「必须是黑发」,那就有问题了。因为你这个条件涉及到了那些天然非黑发者,却并没有排除掉「染成黑发」者。结果就是,从正反两个方面,你想要的效果都没有达到。我们就会说你这个条件订得有点脑残。
美团这个 HR 的第三条和第五条就是如此。开大众的也许有价值观不符的,但也有跟价值观无关的。价值观不符合你要求的人,自然也有开其它品牌车的。「黄泛区」和东北人士可能有很多人你不喜欢,但这么多人你能确定所有人都不对你胃口?其它地区的人就都对你胃口?
所以啊,列出了这两条惹口水的条件,却既不能完全排除你不想要的人,还可能「误伤」本来符合你条件的人。站在公司立场看来,这种HR完全就是在瞎鸡巴搞,本职工作显然不合格,被辞退掉完全是活该。站在我们旁观者的立场,歧视归歧视,要是歧视得有道理,比如「不招共产党员」,我们都会点头称是。你要是说「不招关注过郭文贵者」,我们都会骂你脑残欠抽。

少年们,无论将来干什么工作,请先学好逻辑!