Done is better than perfect

又发现了自己的一个缺陷,不管做什么事情都像绣花一样慢,事情做的是不错,但是效率极其低下。比如,博客文章从开始写到发布至少要经历两三周,各种精雕细琢,但就是迟迟不能发布,总觉得还少了点东西。就是追求完美,一定要做到无可挑剔才行。实际上完美是一种永远都不可能达到的状态,从0到80分很容易,从80到99的时间是指数上涨的,从99到100,一定要耗费巨大的精力和时间。

逃离舒适区

所有生物都是趋利避害的,人也不例外,本能的都想让自己更舒服一点,少遇到一些挫折和痛苦。“不要停留在舒适区”的道理大家都懂,我也从来不认为自己还在舒适区。最近反思了一下,发现舒适区比我的认知要大很多,我以为自己走出去了,其实并没有,还是在这个圈子里面,这比心甘情愿留在舒适区还要可怕。今天来深刻反思下我犯过的一些错误。

该不该用-webkit-font-smoothing?

跟InfoQ中文站、36kr这些国内的站点对比了下,发现我博客上的字体非常的别扭,尤其是在外接显示器的情况下。但是看用的字体也是一样的,都是PingFang SC,为什么呢?于是把css一行一行的拷贝过来测试,最后发现是下面这两句导致的差异。

body {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

之前看起来字体非常的粗,都挤到一起了。加上这两句,体验好多了,变得异常的清晰。在Retina屏幕上变化不算明显,但是在2k分辨率的外接显示器上,完全是两种体验。

体验问题解决了,还想再深入了解下原理,看看MDN上的介绍。这两个样式都不是css标准样式,而且仅在Mac下生效。font-smooth曾经是标准的,但是后来被移除了。MDN上有红框警告,建议在生产环境不要使用该样式。看到这个警告,内心开始产生怀疑了,用这个是不是有什么坑呢?

又搜索了下,发现有人在google font的仓库里面提issue,希望去掉google font页面的-webkit-font-smoothing样式,因为这会导致字体与实际的表现不同。作者也出来回应了,“谢谢你的建议,但我们还是要保留,因为要以最好的状态来展示这些字体”

另外还看到一篇文章建议不要再使用,Please Stop “Fixing” Font Smoothing。里面提到了,让浏览器自己选择才是最好的,次像素防锯齿才是最好的,即便开了antialiased看起来更舒适,但那也是不对的。说了一大堆道理,恳求大家不要再用了。讲真的,我差点就信了。为了确认下作者是不是在说实话,我打开Inspector检查了下,看到了下图。

幸好没盲目的听作者的。文章比较老了,不确定是不是作者写完文章后来又改变了想法把它加回去了。即便如此,那也应该更新下的。这篇文章出现的频率还是很高的,不知道会误导多少后来的人。其实有很多官方的Guideline也存在同样的问题,比如苹果的开发者文档,经常文档里面说一套,官方的app又不遵守。所以这些规则和最佳实践可以去参考,但是没必要盲从。还是要结合自己的具体场景做出选择。

顺便看了看其他网站,基本上都有使用,比如苹果的开发者文档、MDN文档。所以放心大胆的用吧,没有任何的副作用,这才是最佳实践。

Clojure开发初体验

先说说我为什么想试下Clojure。作为Emacs的忠实用户,有时需要用Emacs Lisp实现一些功能,但是Emacs Lisp的文档写的比较简洁,网上相关资料也非常少。很多时候都不了了之了。后来发现,有些基本概念是Lisp系语言共有的。搞不懂的问题,可以查查其他Lisp语言的资料,或许能有答案。

因此萌生了系统性学习Lisp语言的想法,想看看冰河翻译的Practical Common Lisp。但是如果看完只能解决我使用Emacs的问题,好像成本有点高了,毕竟Common Lisp不能拿来干活啊。

除了Common Lisp还有别的选择吗?当然有,那就是Clojure了。做为一门Lisp方言,Clojure有着优雅的语法和强大的表达能力,最关键的是真的可以拿来干活。跟Scala、Kotlin一样,Clojure也是基于jvm实现的,这意味着有大量成熟的Java生态的工具可以直接使用。

书籍

第一个步骤肯定是找些书来看看了。经过我的搜索,这些书都还不错,可以挑一些来看看。

打王者荣耀,我学到了什么

最近这段时间王者荣耀打的比较多,大体计算过,每天在线时长要4个小时,非常的颓废。不过好在现在已经戒掉了。游戏本身其实没什么好讨论的,攻略文章和视频满天飞。今天不谈技巧这些东西,而是分享一些更深层次的体会。

The Perfect Language

如果有一种语言是完美的,那它应该是什么样子的?谈谈我的看法。

防止用户用石头砸自己的脚

C makes it easy to shoot yourself in the foot; C++ makes it harder, but when you do it blows your whole leg off.

这句话出自Bjarne Stroustrup。即便C++让搬起石头砸自己的脚看起来更难一点,但并没有从根本上阻止用户这么干。根本问题在于用户是靠不住的,他可能不知道哪些行为是危险的。C++里面有不少这种一不小心就砸到自己的特性。比如指针和引用,每个程序员应该都有被C++的指针和引用伤害过,用的姿势不对就会引发灾难。所以写C++程序一定要小心翼翼,虽然我已经写了很多年了,但还是保持着“敬畏之心”,每次不确定某个写法的时候都要查资料反复确认。还有一些本来就可有可无的特性,引入之后反而让事情变得更复杂,但是又没有带来任何收益。比如i++++i,写个i = i + 1能又多难呢?这都是导致程序员脱发的元凶啊,生活本来可以更简单的。

那应该如何防止用户干这种事情呢?在指针和引用的问题上,Rust的处理方案是非常优秀的,通过ownership和borrow checker完美的解决了这类问题。只要用的姿势不对,编译都过不了。虽然代码写地略痛苦,但是和上线之后才发现问题相比,这点痛苦完全可以忽略不计了。

有问题就应该在编译阶段就解决,运行时再发现已经太迟了。

Maybe, Option, Optional

如果有一个整数型的变量,如何判断它是否有值呢?下面是一种常见的做法。

int write_x();

int read_x(int x) {
    if (x == 0) {
        do_something_without_x();
    } else {
        do_something_with_x();
    }

    return 0;
}

read_x能否正常运行,取决于write_x的实现。如果write_x也一样认为0就是没有的意思,那就万事大吉,否则就会出翔了。假如由两个团队分别维护read_xwrite_x两个函数,出现问题的机率更大。即使某个时刻双方达成了约定,无法通过系统进行约束,约定就是废纸一张。当然可以尝试通过其他途径进行封装,比如protobuf的has_x(),或者是将x的访问统一封装成函数库。只要有机会可以直接操作x,都无法彻底的避免。如果在语言层面支持,结果就非常不同了。

这里需要的是一种类型,可以完美的描述某个东西不存在的场景。这个类型,就是Haskell的Maybe,Scala和Rust中的Option,Swift的Optional。如果用Scala的话,上面的代码应该是这样的。

def readX(optionX: Option[Int]): Unit = {
    optionX match {
      case Some(x) =>
        doSomethingWithX()
      case None =>
        doSomethingWithoutX()
    }
  }

通过Pattern Match,如果忘记了匹配case None,编译器会提醒你。如果不使用pattern match,而是通过optionX.get获取,当x不存在的时候会抛异常,虽然问题发现的晚,但是可以保证不会执行一段奇怪的逻辑。

Option实际上是一种代数类型(Algebraic data type),借助ADT可以实现各种复杂的类型,详细解释可以参考Haskell的wiki

技术不能改变世界

由餐厅想到的

看着路边的网红餐厅一个比一个火,想到一个问题。对餐饮行业来说,厨子重要吗?厨师毕竟决定了菜好不好吃,应该算重要的吧?但是回头想想,这些餐厅的炒的菜真的那么好吃么?好像也没有。只要厨子做的不是太差,基本上餐馆就不会倒闭了。但是餐馆如何扩大盈利规模,这就不是厨子能决定的了。还在于其他的方方面面,比如如何搞定市场?如何提升食客的体验?包括心理上的体验。对于这些事情,厨子是无能为力的。

如果站在厨师的角度,可能看法就完全不一样了,觉得自己特别牛逼。“这是一门艺术!”。不得不承认,能够把菜做好确实需要能力和经验,也具有一定的挑战。牛逼的厨师当然也有,但这本身就是凤毛麟角了。对餐厅来说,厨师不是短板,这个厨师不行,完全可以换其他的,反正也差不到哪里去。这也就意味着,厨子没法向老板要求更高的薪水。

厨子如果只知道闷头炒菜,一定是没有前途的。

程序员与厨子的共同点

程序员和厨子其实差不多。区别在于,相比炒菜而言,写代码有一定的门槛,需要一定的知识。但是说实在的,门槛高不到哪里去。不要看大家吹水的时候都是讨论各种牛逼的算法牛逼的语言,其实大家都很水。想想自己平时工作所贡献的代码,真的没有什么技术难度。在这个行业里面,往往都是面试的时候要求造火箭,上班之后拧螺丝。计算机因为是最近几十年才开始发展,所以现在还有一定的门槛。但这个工作早晚会变得像厨师、司机等等职位一样平凡,只是时间长短的问题。

有个很重要的问题,工作本身的难度就比较低,程序员的能力并不能得到很好的发挥,价值也就无法体现。如果工作的难度只有60分,对老板而言,一个60分的程序员和80分、100分的几乎没什么区别。虽然你可以造火箭,但是我这里只需要一个拧螺丝的,所以我只能付给你拧螺丝的薪水。

技术优越感

相比厨师而言,程序员更容易产生“技术优越感”。什么叫技术优越感?

一是盲目地认为技术可以解决一切的问题。就像厨子不知天高地厚,认为自己才是餐厅的财神爷,高估了自己在整个链条中的影响力。只把精力放在炒菜上,认为其他的一切都不重要。因为已经在这里累计了一定的优势,所以寄希望于通过自己最擅长的能力解决问题。做事情的时候,往往只关注技术方面,比如写博客,文章可能写不了几篇,但是框架换的非常勤。

二是瞧不起其他的方方面面。写代码这件事,当然有它的难度。完全可以朝着造火箭的方向去努力。因为存在难度,有门槛,有挑战,当掌握了这些“高端”技能的时候,反而忽略了其他的基本技能,非常抵触去做这些“低端”的事情,觉得自己做这些事情完全是大材小用太浪费了。也因为有了这些,勉强可以通过拧螺丝钉来解决温饱问题,根本不会考虑去做些不一样的事情。

100分的程序员更容易产生技术优越感。为什么呢?能做到100分,必然克服了无数的困难,一路上披荆斩棘。自以为Hard模式都已经通关了,还有什么做不成的?另外一方面,一定得是对技术有巨大的兴趣,才能自我驱动,这也就意味这是他们的舒适区。相反的,60分的程序员更容易有“全局观”,不至于在一棵树上吊死,逼自己做不擅长的事情是很痛苦的。

当厨师离开平台的时候,才会发现,“啊,原来我除了炒菜啥都不会”。这下生存都成问题了。

技术更应该作为一种优势,而不是赖以生存的唯一能力。作为厨师,当然可以花心思研究怎么炒出100分的菜。但是只有炒菜100分,其他都是0分,风险是很高的,发展终究要受限。假如各方面的能力都能均衡发展,岂不是更好?炒菜80分,其他的能力60分,这才是比较健康的状态。

题图来自Unsplash,作者Michael Browning。

面向未来思考

我不是一个善于规划的人,更确切的说,我非常讨厌做规划。总觉得“计划赶不上变化”,规划是一件非常虚无缥缈的东西。回顾一下过去,我所能思考到的未来大概也就是一周左右吧,“鼠目寸光”。最近思考一番,发现这是一个非常严重的思维缺陷,虽然内心非常不愿意承认。一个国家都需要五年十年一百年的长期规划,个人难道不应该做思考么?三年之后,我希望变成什么样,五年十年呢?从来没想过。

这就好像一辆车在路上行驶,只挑眼下最容易走的路,但是并不考虑目的地在哪。虽然好像走的很爽,但是走偏了。总结下原因,大概有这三个:

  1. 学生时代并没有养成这种习惯,这个阶段的人生基本上都已经被长辈被社会规划好了,不外乎中考、高考、大学毕业找工作,结婚生子。自己只要朝着这些方向努力就行了,根本用不着自己考虑。我想这应该也是大多数人的情况,天赋异禀的神童除外。
  2. 少不更事的年纪盲目听信了微博上技术大牛的“毒鸡汤”,主张“追求技术的同时顺便把钱挣了”。我信你个鬼,糟老头子坏的很。
  3. 工作事务繁忙。大脑的“带宽”被疯狂的挤压,视野只能停留在具体的事情上,想着尽快把当下的事情解决掉,无暇顾及将来。

工作多年,一直把提升技术实力,解决技术难题作为自己的目标。持续学习,花了不少时间在研究各种技术上面,遇到过很多困难,但是也算过得开心,起码时间投资是值了。坚信职位和待遇这些只是能力的附属品,能力上去了,这些自然都会有。自己从来没有反思过,这种想法到底对不对。最近有点疲倦,发现好像这些事情对自己吸引力没有那么大了。所以开始反思,做了这么多努力,为何现状与自己的预期有这么大差别?

追求这些东西对个人发展有益吗?当然有益。但是就工作本身来看,这些绝对是错误的目标。犯了“用战术上的勤奋掩盖战略上的懒惰”的错误。在公司工作,目标是什么?当然是升职加薪,做成打工皇帝啊。很直白的道理对不对?这些和技术能力有关系吗?有一定的关系,但绝不是决定性的。更为准确地说,关系非常有限。如果工作之初就能看到五年十年之后的话,一定不会有这些天真的想法了,在经历的很多事情上,也一定会做出不一样的选择。

仅关注眼前的事情,还会有个弊端,就是会把短期内的事情看的非常重,不自觉地放大这些事情的难度。但是回头想想,当时觉得是困难的事情,其实什么都算不上。跟未来的挑战相比,这些都是小菜一碟。

在公司工作,那就一定要好好的“混”,朝着那个目标而去。在技术上钻研,写博客,玩摄影,那就要争取在各个圈子里面产生一定的影响力,而不仅仅是沉浸在自己的世界里。看的要远,目标一定要明确,这是对自己的新要求。

题图来自Unsplash的Johannes Plenio。

新加坡见闻

亚洲的城市中,香港和新加坡可以称得上是风光摄影师的天堂。香港离深圳比较近,拍过很多次。去新加坡拍风景则是我的梦想。对于新加坡的所有印象,都来自于一位新加坡的摄影师 wonglp。他同样也是M43用户,所以我一直把他的照片作为标杆。我个人的摄影风格也受到他潜移默化的影响。他所拍摄的城市风光,让人感觉新加坡是一座来自未来的城市,所以很期待有朝一日也去新加坡拍一次。

五一小长假由于没有提前规划,就在深圳度过,感觉浪费了一个假期。所以五一过后就马上开始准备去新加坡。开始以为需要花很多时间准备攻略,后来研究了一下地图,发现根本不需要,新加坡本身非常小,几个景点地理位置上都比较近,而且公共交通非常发达,基本不需要操心了。出发之前在YouTube上看了一些视频,特别推荐cctv4的远方的家系列节目(远方的家一带一路038-048新加坡),简直就是旅游界的“舌尖上的中国”。

对一个国家的第一印象,一定来自于机场的体验。樟宜机场的用户体验真的可以用吊炸天来形容了,连续七年蝉联Skytrax“全球最佳机场”的称号。不夸张的说,单单这个机场就值得花一天的时间在里面。今年4月份星耀樟宜(Jewel Changi Airport)开业后,机场的可玩性更高了,最著名的应该就是这个网红室内瀑布。不过人真的很多,很多商店都要排队进入,比如SHAKESHACK、POKÉMON(实在搞不懂买个玩具也要排队)。

Blame Oriented Programming

面向对象编程大家都知道,之前还听过面向“离职”的编程,意思就是写代码的时候要假设自己马上要离职了,考虑的接手同事的感受,应该把代码写的简单明了,多做注释,多写文档。今天给大家介绍“面向锅的编程思维”。

作为后台开发人员,自己的后台服务通常都会调用其他人的模块。在系统设计的时候,面临服务之间调用的异常,有两种做法。

  1. 根本不考虑异常情况,调用失败也不处理。假装所有接口都一定会成功。
  2. 考虑异常,但是觉得服务是对方的,挂了的话责任应该是对方的。所以只做简单的重试。

第一种做法必然不可取,一般也只有刚工作的程序员才会如此奔放。大多数人可能选择第二种做法,但是服务上线有问题后很可能锅就落到自己头上了。为什么?因为对老板来说,他并不关心具体原因,只知道这个功能有问题,你俩都要挨板子。这个时候很可能就说不清楚了。比如对方说,我的服务就是需要由调用方保证成功的,或者说我的服务不保证100%的成功率,如果你要求100%那就不应该用我的服务。这样一来,主要责任反而会落到自己头上。

那应该怎么做?对于每个可能失败的地方,都要做好以下思考。

  1. 可能失败的地方是否关键路径,失败了影响大不大?会不会影响升职加薪?
  2. 能否进行重试,如何进行重试?特别需要明确的是,重试的考虑,是有可能超过主流程的工作量的,因此这也是评估整体工作量的非常重要的一个因素。
  3. 如果重试不成功,那应该如何处理?如果最终没有解决方案,那至关重要的一点是让各方都知晓此处存在的风险,由整个团队来进行评估,不要自己一个人做决定。

这些其实是我自己的痛点。一开始没有考虑好,最终要花费很长的时间去修复这种问题。更别提这些问题已经造成的后果了。如果早一点意识到这些,那该多好。