作者:chifungcheung

出MacOSX记

“出”是Exdous的“出”,不是Sell的“出”。日本闲鱼不给力,挂的iMac还没卖出去。

新机器

新买的PC终于到手了,XPS 8930 Special Edition, i7-9700K + 16G DDR4 2666 + 512G SSD + 2T HDD + GTX 1660Ti,Dell官方BTO又上了个Windows 10 Pro,各种优惠弄下来含税16万日元左右。

BTO还是比较久的,大概是大陆还是台湾发货,等了两周到手,挺小巧紧凑的一台。

后来觉得内存条空着不爽,补了两台鑫士顿16G插满到48G;又想两个3.5硬盘位太寂寞,把闲置3T录像用的硬盘拆了装上去。却又发现没有SATA3线,亚马逊一逛都是两三条起卖,顺手又买入个8T的低速硬盘插上,2T+3T+8T=13T HDD。

装了个Ubuntu,桌面装了也没啥用,而且个人很讨厌Gnome3,换成Text Mode启动,开SSH服务,装了MicroK8s,当开发服务器。

装老黄驱动,跑CNN训练,用9700K跑半负载4核都要跑20分钟的单个epoch,用老黄黑科技30秒就搞定了。有点后悔没有一狠心加4万上RTX 2070,真是人穷志短。

俗话说,买前生产力,买后玩游戏。进Windows 10开了下昆特牌3,画面是不错,想起来没音箱,只能亚马逊花了一千多日元买了个十多年没摸过的玩意。加钱换Windows 10 Pro是为了remote desktop和hyper v。WSL2试了下,居然装不了一装就重启。本来也没打算在windows下做开发,也没怎么折腾了。

Mac的痛

本科四年一直用Gentoo作主桌面,想起来现在聊得比较多的网友推友,也是当时玩Linux认识的。后来跟了我四年的神船不堪重荷,而当年玩Gentoo的网友都换成了Mac,自己也受不住诱惑,2011年买了第一台Mac,当年最新的MacBook Air 13。作为Linux用户最大的感慨,就是一台电脑居然可以同时拥有这么优秀的命令行和界面,还这么漂亮便携,电池还能跑一整天。当时用来用Unity做游戏,用vim写ruby,完全就是一种享受。需要用算力的时候,可以连研究室的服务器或者学校的超算,从没感受过性能限制。

可是2019的今天,又是另一翻光景。新买的iMac 5K,一天freeze两三次,差不多花了一年才修好这软件问题;而MacBookPro TouchBar 15则是从一开始的电池问题,到后来的键盘问题,都让我担惊受怕。跑了几趟天才吧,虽然服务好,但能感受到近年的Mac真的很难拿来工作了。以前的Mac买来是当生产力,现在感觉就是受罪。

最后是性能问题。虽然16G内存平时还行,跑个minikube和几个docker就跪,还不能升级;做深度学习开发,又只能用苏妈显卡。这也是我最后决定再买一台PC的最大诱因(绝对不是为了打游戏)

其他选择

Windows 10其实已经很不错了,很多以前诟病的问题也都修得差不多,而且还有WSL2;Linux现在的Gnome则用上了Wayland,图形表现很不错。

可惜的是,用了两周这个系统,我得出的结论是:

有钱买得起Mac做桌面,且你只能选择一台机器的话,那还是用Mac吧

原因有几点:

  • 字体渲染还是MacOSX一骑绝尘。Ubuntu也不算差,但是不想折腾;Windows 10就是一如即往的垃圾,装了MacType也是那样。
  • 4K的对应MacOSX是完美,Ubuntu的Gnome还行,Windows 10我觉得不合格,还是不时会跳出来一些上古时代的低分窗口让人出戏。最奇怪的是Windows 10高清下打开Chrome或是Edge时,全局DOM的默认缩放(zoom属性)居然是1.5,不知道微软怎么想的。
  • 不想折腾输入法。
  • WSL2还不成熟,还需要等待。而且WSL2是不支持在Ubuntu下开显卡的,对我来说就是废物了。
  • Visual Code Remote还是不够给力,只能打辅助。
  • MacOSX的包管理某种意义来说甚至比Ubuntu完善,很多在brew可以直接安装的包,在apt都找不到。
  • 在公司用的Mac,不想来回倒腾。

分开来看,Ubuntu下做开发,Windows下打游戏听听歌,都是可以的,但是如果只能用一台机器一个操作系统,那Mac还是比较好的选择。毕竟键盘烂,可以外接HHKB;而字体渲染烂,就真是活受罪。前端用Mac,用Ubuntu补足性能,还是一个目前比较适合我的选择。

Kubernetes,Ad Manager,Google的解耦艺术

Kubernetes与Ad Manager是Google开发的两个不同领域的产品。Kubernetes大家都认识,Google的开源容器管理平台。Ad Manager即没那么有名气,它的前身是Google基于收购的DoubleClick所开发的DoubleClick For Publishers, 是一款AdServer产品,做互联网广告行业的人应该都多少接触过,虽然是有点历史的产品,但使用的人还是非常多。

本文试图通过这两款产品,浅析Google在设计商业软件平台时的逻辑,同时分享一些个人思考。

两产品的相同之处

这两款产品看似风马牛不相及的产品,但处理的问题其实有很大的共性:就是处理资源的供求关系。

KubernetesAd Manager
SupplyNode, PersistentVolumeAd Unit
DemandPod, PersistentVolumeClaimLine Item

在传统的运维里,硬件与软件基本上是绑定的。软硬件团队必要在充分协商后才能部署。而在Kubernetes下,所有硬件都被抽象成不同的资源供应,而需求方只要声明要用的硬件和想部署的软件,剩下的Kubernetes全部搞定。

https://thenewstack.io/strategies-running-stateful-applications-kubernetes-persistent-volumes-claims/

Ad Manager处理的问题也有点类似。网站管理员在网站上设置广告位(Ad Unit),也就是库存(Inventory)。广告销售出售拉拢需求方(Demand),也就是广告主(Advertiser),然后根据广告主的要求和预算把广告分发到这些广告位里。

为了帮助用户更好地进行匹配,这两产品都提供了很多用来设置规则的工具,比如Ad Manager里的Key-Value还有Kubernetes里的labels,都可以让供给自由地分配到吻合条件的需求方。两者也有一些简单的算法,去最大化整体的匹配的合理性。

与互联网信息平台的不同

谈到Supply与Demand,很多人会联想到很多互联网平台应用(Platform)。互联网平台的本质在于消弥信息不对称,撮合供求关系。从电子商贸平台,众包平台,到现在以Uber、AirBnB为代表的共享经济,都是这一类。

关于Platform的畅销书,建议一读。

尽管都是匹配供求关系,但是Kubernetes与AdManager显然与这类“Platform”是不同的产品:

Kubernetes & Ad ManagerPlatform
设计目的解耦资源 & 高速匹配效用最大化
参与者利害关系同一利益不同利益
参与范围企业内部用户所有平台用户
匹配方法以匹配度为主导以价格为主导
人工参与程度以自动化为主以人为主
人均匹配量
匹配标的虚拟资源可交易的实物或虚拟物

Kubernetes & Ad Manager的设计目的,尽量把企业内部资源进行责任分离,与不同的角色关注不同的资源;通过计算机把供求关系当成一个最优化问题(optimization problem)去解决,节省人力物力。这种问题属于传统管理学和运筹学的研究课题。

而互联网平台则是通过自由市场原理,通过增加市场的流动能力,用价格机制解决供求关系。这里更多是一个经济学问题或者博弈学问题。

两种模式的融合

上述的两种模式也不是完全的非黑则白。比如Ad Manager,可以通过RTB价格机制来获得外部需求来最大化利益。RTB(Real-Time Bidding)是次贷危机后金融行业的Trader转行互联网广告行业时发明的玩意,对于用户的每一次访问都开始一次拍卖(Bidding),胜者(根据价格设置决定,不一定是最高价的人)赢得本次曝光机会。

Kubernetes虽然目前没有类似的机制,但既然AWS已经有Spot Market可以竞价开ec2,可以想像在不久的将来Kubernetes也能通过价格机制调用外部资源,最大化整体利益。

资源的解耦的结果是增加了流动性,而流动性是价格机制的第一步。Google通过解耦把这些传统上强耦合的资源分离,再逐步用网络至它们上市流通,实现利益最大化,真是神来之笔。

延伸

像企业内部资源的优化问题,传统运筹学已经研究很多,比如人力资源、生产资源的调配,都是老话题。但是这些研究的课题都着生产资料、金钱的分配,而非资源的虚拟化与流通化。Kubernetes和Ad Manager都在这些问题上给出了不错的答案,而类似的方式还能用在其他问题上,这还有待大家的尽情想像。

Docker跑NodeJS的同步问题

一直MacOSX本地做开发,没遇到过Watch同步文件不了的问题。最近在skaffold + minikube + React下跑开发环境,居然无法触发WebpackDevServer的Watch刷新,折腾了些时间。

用的是create-react-app建的基于TypeScript的环境,eject了webpack设定,本地跑能watch,在skaffold的manual sync下模式就不行了。skaffold有两个模式,一个是默认模式,一旦有文件修改就重建整个容器,能动但是慢;另一个是manual sync,可以把文件自动同步进容器内,触发容器内的开发服务器重编译或者HotReload,是做本地开发比较现实的方案。

开始以为是skaffold设置问题,但是进容器内能看到文件的确被同步成功;又以为是create-react-app的问题,检查了也是能跑的。奇怪的是明明同样的设置,我的机器却跑不了;明明跑python flask没问题,node却不能动。

后来调试时一不小心触发了提示:

Error: ENOSPC: System limit for number of file watchers reached

ENOSPC指的是没有空间,常见的一般有两种问题:一是真的没空间了,还有一种是inode不足。而这次这个则是file watcher不足。

Webpack官方对这个问题给出了解答:改内核的监视器数目限制。我用的node:10.15.3-alpine,默认数量才8192,对很多Node应用是不够的。其实同样的问题以前用golang在ubuntu做服务器编程,监视log文件时也遇到过,没想到会在写React时再被恶心一次。

因为是内核问题,在Docker容器内部是改不了的,必须进node里改。Production跑的是编译版可以不用动,只改开发用的minikube就可以了。minikube ssh进去

echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p

一下就好,不用重启,连进pod就能看到更新。

不止是docker,理论上来说用ubuntu这类内核默认设定比较保守的linux发行版做开发都应该很容易遇到同样的问题。据说VS Code在Ubuntu跑的时候就会显示:

Visual Studio Code is unable to watch for file changes in this large workspace

新的PC还没到,提早碰上这个问题也是个好事。打算去skaffold开个ticket警告各位。

穷鬼k8s

穷鬼k8s

k8s是好,自己用还是挺贵的。本文的目的是帮助各位像我一样的穷鬼去架一个自家用k8s,工作学习两相宜。

目标

虽然是穷鬼k8s,但是作为达到了马洛斯需求层次理论前四个层次的穷鬼,还是要达到以下的目标的:

  • 可以跑一些平均QPS大概在10以下的小型Web服务。流量不大。
  • 可以接受一定程度的延迟,基本只服务本地Region。
  • 基本上24个小时跑,但接受一定的downtime。
  • 有一定的扩展性,如果服务盈利了可以比较方便的Scale Up和Scale Out。
  • 不考虑中国国情。
  • 长期使用,不考虑运营商的返鑫。
  • 尽量简单。

在满足了上述的前提条件下,才是尽量便宜。

本文假设读者已经有一定的k8s知识,没学过的话建议读读官方教程。

Provisioner

首先要决定的是Provisioner,也就是服务提供商。主要分两类:

  • 使用云托管的k8s。像Google的GKE、Amazon的EKS、还有Azure那啥。
  • 自建k8s。这里面又有好几种,比如像rancher一类建在云主机上的Turnkey cloud,还有是在DataCenter上组的On-prem cloud,等等。

k8s官方有个表格,大家可以看看。

有些人推荐自建,但除非想用k8s吃饭,或者考CKA,自己架k8s是非常吃力不讨好的。我认为个人使用的话只有云托管一个选择。

  • 自建k8s坑太多,而且运维起来非常麻烦。本来用k8s就是为了把平台抽象化加速DevOps,自建k8s的话那些恶心人的东西都会回来。Turnkey Cloud听上去很好建,像Rancher2那种一键造Cluster听上去很美,但除非是企业的成本限制,否则不建议尝试。
  • 现在的托管k8s有很多便宜的选择,像DigtialOcean,KubeSail,都不贵,丰俭由人,当然好不好用是另一个问题。
  • 建k8s不但要考虑Node的成本,还要算上Master。k8s要跑好,Master必须要用好主机,4G内存起步,3台或以上最好,有这些钱还是买Node吧。
  • 周边设施也要花钱,跑k8s要有Container Registry,要有Disk,要有LoadBalancer。这些钱自己建都不便宜,而且你还不一定建得出来。

选择了托管后,就要选运营商。我自己选的是GKE,理由很简单:

  • 稳定压倒一切。
  • 没用过EKS和Azure,而他们的价格都不比GKE便宜。GKE的Master可以免费的,只收节点钱。
  • GKE性能强大,在公版k8s基础上,有支持AutoScaling的NodePool,有抢占式的Node,这些都能很好的省钱。
  • 上面提到的Container Registry什么的性价比非常高,而选择DigitalOcean虽然节点便宜,但周边就差很多。当然你也可以用多个运营商来弥补不足,但是网络费用也不便宜。
  • 有东京节点,这个可不容易,只能怪本地选手不给力。
  • 可扩展性强。

Architecture

我读了很多关于如何省钱的方法,主要是几点:

  • 自建或选择便宜的小运行商。前面也说了,这其实也不便宜。忽略。
  • 选用低配节点。省是省了,但是满足不了我们对有一定可用性的要求。k8s默认的Deploy方法是Rolling Update,你要有足够的空闲资源去建新pod。在节点配置上省钱不现实。
  • 减少节点数。这个其实比低配节点现实很多。GKE的节点价格跟性能大概是线性关系,而每个节点却都要装占一定资源的kube公共模块。反正单节点也一样能跑k8s,同样价格上少节点比降低配置实惠。但是单节点的话跑k8s就没有意思了,而且用单节点的话很多问题其实会注意不到,不建议。
  • 减少公共组件。GKE默认使用下会自动加载Flunted等组件来记录Metrics,穷鬼k8s心比较大,完全可以关掉它们。现在GKE的创建Cluster的Wizard已经有了“第一个集群”这种选项,选这个模板的话这些用不上的组件都会被关掉。
  • 不用LoadBalancer。这一点是非常容易忽略的。很多托管k8s提供商都用自身的L7的负载均衡组件来实现Ingress,而且是按Rule收费,大概是15~30美元不等。我用GKE的免费额度时就被坑过。LB虽然功能强大,但穷鬼k8s不需要,去年LB能省不少钱。
  • 使用抢占式节点。GKE有个特点,可以用抢占式节点。同样的n1-standard-1,东京区普通版是31刀,而抢占式只有不同10刀。但是抢占式节点会在24个小时内关机,也就是每天都会重启。在GKE下k8s会自动给我们重启节点,所以不会有大问题。然而重启会IP会变,不可能全部用抢占式。
  • 使用AutoScaling。毕竟是穷鬼k8s,负载不高,我们可以用AutoScaling关掉不用的节点。虽然GKE支持,我还没有用过就是。

综上考虑,最终的图是这样的:

穷鬼k8s
  • 我用DaemonSet跑Caddy代替了LoadBalancer,设置dnsPolicy: ClusterFirstWithHostNet后就能直接从节点连接到Cluster内网络。记得要设置Node的防火墙规则,让外部访问能直接进来。
  • PrimaryNodePool只有1个普通的f1-micro,5刀不到,还每月送一个。普通节点的公共IP是固定的,可以直接绑到域名上。
  • 这里用Caddy代替Nginx主要是因为Caddy可以很方便地实现HTTPS,省却很多麻烦。用Caddy的HTTPS功能有一点要注意的是要把TLS证书缓存到硬盘上,否则每次Deploy都会去更新证书而触发LetsEncrypt的RateLimit。
  • PrimaryNodePool上设定Label,CaddyService设定NodeSelector,CaddyService就能绑在PrimaryNodePool上。
  • 其他的Pod全部跑在抢占式的PreemptiveNodePool上,这里用了两个n1-standard-1。因为是抢占式,要注意做好Replica。

这个1拖2的设计,节点成本是5刀+2*10刀=25刀,加上Disk和Network一类的都不到30刀,完全够用了。想要在这基础上省钱,可以把PreemptiveNodePool换成AutoScaling或直接砍一个。再砍的话可以把n1-standard-1换成g1-small,比较恶心的是抢占式主机的价格地域差比较大,同样的g1-small在Iowa才5刀多点,在东京要7.3刀不便宜,所以我直接上n1-standard-1。

最后

有了穷鬼k8s,还是要做开发的。现在有Skaffoldk9s这些工具,在k8s上做开发比以前要简单很多。大家可以自己组一个试试玩。k8s代码我会在整理好后再放上来。

Go的软件架构方法论迷思

Go的软件架构方法论迷思

奇怪的思潮

最近东瀛Go业界有种奇怪的思潮:把Clean Architecture带进Go。我一直以为这只是某些小公司的自娱自乐,后来跟一些同行谈了一下,发现也不是个别现象。一个项目能不能用、活不活得下来还不知道,domain、repository、entity、use-case倒是整了一大堆。

其实Go的设计的最佳实践,官方文档和博客已经写得非常明白,从包名设计,到错误以处理,都有一套官方标准。这也是Go这门语言的优秀之处:简单,节制,让所有人都在一个约束下跳舞。至于应该用何种软件架构,却甚少见人提及。

如果大家读Go标准库或者一些优秀的Go的OSS代码,像HashiCorpCoreOSPingCAP,甚至是面向初学者的CURD项目RealWorld,会发现并没人把Clean Architecture等方法论用在这些项目上。这些代码点到即止,都在努力用最少的代码用最简练的方式实现它们所定义的目标。不止Go,著名的C、Rust、Python项目里我也没见过所谓的Clean Architecture。这股工业界的暗涌,跟OSS最佳实践显然是割裂的。

Clean Architecture试图解决的问题

不管是Clean Architecture,还是DDD、TDD、MVC,其诞生都有其道理。所有的软件设计框架和设计模式,它们都是对现实中某类复杂的问题的一般解。这些解不是也不可能是银弹,有其制约性。

我们看看Clean Architecture试图达成的主要目标:

  • 从粗到细的开发流程,延迟细节决定。
  • 责任分离。
  • 可测试。

首先,从粗到细的开发其实并不需要什么架构,只要写好伪代码就好。很多程序员并不写伪代码,一上来就写细节,这是一种不好的习惯。不仅程序,文章、计划书,也是这么写的。写程序不是DFS,而是BFS。很庆幸我初中的第一本JAVA书教会了我这个道理。

其次,责任分离在Go下只要用好interface就好,也跟架构关系不大。

最后,可测试当然很重要,前提是你TMD要写测试啊!各位Clean Architecture信者问一下自己,你们真的有写测试吗?有吗?!

Go的解

Go的设计之初,就考虑过各种软件架构和设计模式的问题。作者给出的答案,就是Go本身。比如刚才提到的interface,其目的之一就是为了解决责任分离。一个语言特性,胜千万架构与模式。

大家可以翻一下Java的23种设计模式,再对照Go的实现,就会发现很多原来在Java下得用设计模式解决的问题,在Go下都在语言层面下解决了。

让上帝的归上帝,凯撒的归凯撒

难道Go就不用设计模式了吗?也不全是。Go给的解也非银弹,不能解决所有问题,其设计目标是系统语言,而并非全栈语言,它并没有义务对它设计目标以外的问题给出一般解。

来看看号称“世界上第一个全栈语言”的Red所给出的图,看看语言的栈都有哪些:

Scope of Red compared to other programming languages

虽然这图没给出Go的栈范围,但我们知道它是在OS到Applications之间的非常狭窄的一段。

正因Go并非全栈语言,抛开设计模式用Go做其不擅长的业务逻辑密集的应用就会显得捉襟见肘。Go的Channel,Goroutinue,为系统应用设计的高级特性全用不上;而没有泛型、没有模式匹配、没有高级的错误处理,则让人如鲠在喉。

Google发明并送给所有人一台从清洗到叠衣服全自动的洗衣机,但你只关心它能不能把土豆洗干净。

也许有人会说,语言特性不重要,关键是写程序的人。然而,历史上所有的杀手级应用,都是有其相匹配的语言基础作支撑的。比如DHH写Rails时,找不到一门语言去实现它的想法,直到遇上的Matz的Ruby,其强大的元编程特性才成就了Rails。而Rails的大部分模仿者,都因为语言的原因无法达到Rails一样的高度。

全自动洗衣机让最厉害的工程师改装一下当然也能洗土豆,但我相信没一个工程师会想在自己简历上写自己干过这种浪费人力物力蠢事:毕竟出门左转就有农用器械专卖。

原罪与未来

问题的原罪,就是Google的工程师太优秀,没有考虑到这个星球上还有这么多写程序不动脑子的拿来主义码农。这问题显然是无解的,未来也不乐观。但既然献身了开源,自然就得有被全世界批斗的觉悟。Go从开源到现在已有十载,早已不是那个仅为了满足Google需要而开发的语言,它开始承载越来越多不在当初设计范围的特性。我们看到了Go2的改变,还有像v语言这种挑战。

Go开发团队是节制的,然而同人逼死官方的事也不是没发生过,Java就是一个活生生的例子,它在发明的时候也不是企业级开发的代名词。在破乎上对于Go是否要用DI这个问题有过激烈的讨论,其中某大V的一句话震耳发聩:

等什么时候出现了50万行的go程序,用户压低价格导致工资还开不了太高的时候,你们鄙视的东西统统都回来了。想想Java,也不是天生需要设计模式的。

大家还是洗洗写Rust去吧。

VSCode真香

第一把锤子:Vim

刚学写程序时,用过各种IDE,上古神器Visual Studio 6、Delphi、NetBean、Aptana Studio,等等(NodePad++就不提了)。接着就误入歧途,跟大佬混耳濡目染,学会了vim,神器真香,于是就一直用vim当锤子,尝试钉各种钉子。

后来工作,做游戏Client写C#尝试用vim,被现实扇了一巴掌;再做游戏Backend用Java尝试用vim,又被现实扇了一巴掌。被扇多了就学精了,C#用Windows + Visual Studio,Java用Eclipse,Scala用IntelliJ,ObjC用XCode。见人说人话,见鬼说鬼话,别用意识形态折磨自己。毕竟大部分IDE都会有很好的快捷键和vim key binding,像我这种熟练工,经过练习后敲Eclipse的手速不比JR的售票员慢多少。

第二把锤子:Spacemacs

再后来做SaaS,作为一名拿着TechLead的钱操着PO心的一线码农,用的Golang+Python+TypeScript+其他,需要一个全栈的编辑器。vim能做,但设置麻烦。这时同事推荐的Spacemas就成了我的新锤子。

Spacemacs本质是一个魔改过的Emacs。因为Emacs是一个优秀的操作系统编辑器,Spacemacs只通过一堆Lisp代码就把Emacs改得它妈都认不出来:改成了vim,让emacs同时支持了vim和emacs两套模式。

Spacemacs主要有四个特点:

  • 可记忆性:比如b for buffer,p for project。
  • 可发现性:很容易查询到各个命令的功能和对应的快捷键。Spacemacs的一个牛逼之处就是用Space当Leader键,vim模式下大部分的功能都能用以Space键开始的组合键完成。
  • 一致性:对于不同的功能及语言,一套约定俗成全部搞定。
  • 社区合作:众人拾柴火焰高。

Spacemacs的体验是无与伦比的,大部分语言都能通过简单地在设置文件上打开一个开关完成。比如Golang,你只要按照官网把相关组件装好,打开设置,基本就能用。至少对于Golang、Python、TypeScript来说,只用Spacemacs官网的设定就能完成像vim要做很久的配置。

Spacemacs还强在它是Emacs。所以Emacs的优良生态都能上,像Helm,Org,都能在Spacemacs上轻松用得到。

第三把锤子:Visual Studio Code

去年换了工作,跟外国同事接触多了,更感觉越是优秀的工程师,越喜欢用智能的编辑器orIDE。Junior是不知道其他工具才用的IDE,而Senior是为了最大限度地提高效率才用的IDE。

而用Spacemacs这种编辑器的尴尬是:无论你写得多快,你都没有机器写得快。Spacemacs再厉害,还是靠的各种lint和lsp。手速再快,也耐不住智能提示乱拳打死老师傅。作为一个曾经的IDE用户,这种被智能提示征服的快感我还是清楚的。

智能提示做得好的,IntelliJ Idea绝对无冕之王。钱不是问题,奈何这货真的吃资源,打开了基本就什么都做不了;再者我买的时候Goland刚出,bug漫天飞。后来只有偶尔写Scala时才会打开一下,买了一年的Ultimate,用了大概也就10个工作日。

比Spacemacs智能,比IntelliJ Idea轻量的,据我所知也就只有VSCode了。VSCode一直想试,上手了两三次,还是换回了Spacemacs。主要是没看出来VSCode比Spacemacs多的功能在哪里,而我不想多背快捷键。

而近来再换成VSCode的冲动,是为了一个功能:Debug

Debug是vim/emacs一类编辑器之痛,不仅Golang,像Python、PHP、Java都无法Debug。而VSCode集成了非常好用的Debug工具集,调试程序非常简单。用习惯vim/emacs的人可能会觉得没有Debug没有所谓,但这往往就是老师傅被乱拳打死的原因。

有人会说写好UnitTest就不用Debug了,然而这又有另一个问题:VSCode写测试的体验要好很多。VSCode不但可以很轻松地运行or调试一个测试,还可以实时标记有哪些地方已经被测试覆盖。VSCode还自带了很方便的自动测试生成器。用Spacemacs写测试是体力活,而自从用了VSCode,我开始喜欢上了写测试。

带Terminal也是一点,有些IDE朋友可能会觉得很可笑,但这的确是一个折腾了vim/emacs用户几十年的问题。

Live Share & Remote 也是很牛,可惜用到的机会还是不多。

最后是,有越来越多的官方或大公司出品的插件出现在VSCode上,像docker、k8s、jira等开发流程用到的工具都能在VSCode上很方便的操作,能省不少时间。

虽然有些功能IntelliJ Idea也有,但是VSCode不用钱还快啊。

我建议各位vim/emacs用户,有钱的换电脑换IntelliJ Idea,没钱的换VSCode。多让电脑写代码,别为了炫耀键盘什么都自己打。

早点回家早点抱老婆老公比什么都强。

为什么说gorm的设计很糟糕

如果大家写Golang有点时日,很有可能听说过https://github.com/jinzhu/gorm,一个非常有名的Golang的ORM库。我自己是不用的,一来是不会用选择用Golang做CURD开发,吃力不讨好;二来是我觉得gorm的API设计很糟糕。

写一个ORM库是很困难的,不但要对不同的关系数据库特性有高度归纳能力,而且要对所使用的语言有深入的理解。ORM是这两种不同专业领域之间的桥梁,其复杂性可想而知。

可能是因为实现难度高,Golang下一直没有比较好用的ORM库。要么是直接使用官方的 https://golang.org/pkg/database/sql/ ,要么是用简单的上层建筑如 https://github.com/jmoiron/sqlx 或某种QueryBuilder如https://github.com/gocraft/dbr。这些库都各有优点,但抽象能力都远不如Rails里的ActiveRecord或者Python里的SQLAlchemy,在这种背景下gorm就显得鹤立鸡群:又能关联表,又能eager load,官人要啥就实现啥。

可惜的是,ORM是个硬骨头,gorm想做的很多,但能力却没跟上。

ORM是复杂的。gorm最大的问题,就是试图对复杂的问题抽象却没法给出完美的方案。这让我想起我学习Rust时印象最深刻的关于字符串的一节:

Rust has chosen to make the correct handling of String data the default behavior for all Rust programs, which means programmers have to put more thought into handling UTF-8 data upfront. This trade-off exposes more of the complexity of strings than is apparent in other programming languages, but it prevents you from having to handle errors involving non-ASCII characters later in your development life cycle.

https://doc.rust-lang.org/book/ch08-02-strings.html#strings-are-not-so-simple

字符串处理的本质是复杂的,Rust在设计这部分的API的时候,刻意选择了暴露其复杂性,而不是隐藏细节。这虽然增加了开发者的学习成本,但收益是更高的软件质量。

而官方库及其他的上面提及的库其实也反应了类似的思想:只要开发者对数据库有一定的理解,就能想像出每个API背后生成的SQL。这种API的结果是准确的可预测的,可能不方便,但不会让开发者产生困扰。

而gorm选择的是最大限度的抽象,提供的是一个大而全的DB对象,用一种类似Chaining的设计,几乎所有的API都调用它,而它所有的结果都返回它。它是不可知的,你不知道你的数据从哪里来,到哪里去,发生了什么事。 gorm的作者试图用黑魔法把关系数据库的细节都隐藏起来让傻瓜都能用,然而傻瓜根本就不应该用数据库。

gorm的这种设计显然违背了单一功能原则(Single responsibility principle)。我们看回去官方的API:DBConn方法返回ConnConn调用Begin返回TxTx调用Query可以返回RowsRows可以查看结果,可以查看错误。每个结构体都只有最低限度的功能,但是只要一看文档就能知道要用什么方法生成什么,一目了然。

黑魔法是很危险的,就算优秀设计如ActiveRecord,用不好一样会出N+1问题。gorm的这种设计更危险,你不知道哪个方法会返回错误,哪个方法不会。这种缺乏一致性的设计让开发者困扰,也违反了Golang对错误处理的最佳实践,一不小心就会出bug。

gorm的这种设计导致的另一个问题是:你要弥补设计上的问题,不能改API,那只能牺牲性能。比如说你打算用goroutine同时Update两个表,因为返回的都是*DB类型,你会很困惑到底是不是同一个实例,结果里的Error会不会有race condition问题。于是你打开源码一看,发现gorm好样的,它会给你自动clone一份。你又看了一下其他代码,好样的,不管有没用,居然全给你clone了。

gorm还有一些不大不小的问题,就按下不表了。自己也不想对开源代码太苛刻,毕竟谁没写过烂代码?最后介绍一本书,总结了很多Golang下的设计模式,共勉。

经济指标与健康指标的异同

本周读书,《经济指标简史》。

读后发现经济指标跟健康指标还是有很多异同点的(这里的健康指标指BMI、体脂率等指标)。

  • 历史都非常短。像失业率、GDP、CPI等指标都是上个世纪发明的。而BMI和体脂率虽然找不到历史记载,根据Google Ngram显示两者都是战后才出现的词。
  • 虽然历史短,我们都以为是理所当然。
  • 都是评价一个大型复杂系统的健康的指标,有些指标很难测量,也测不准。
  • 测不准的指标都有更直观的观察方法:比如消费者物价指数,可以去超市买菜;而体脂率虽然很难测,但肚子上的肥肉一摸就摸出来。
  • 有些指标就算测出来了也没什么用。
  • 虽然某些机制已经被深入研究,但很多地方还是玄学。凯恩斯的经济理论跟生酮饮食差不多:对症下药可能有用,一不小心就失衡。
  • 经济指标有商业版,健康指标亦然。

读《Factfulness》

读《Factfulness》

这可能是2018年末最红的书,在这弃旧迎新的假期里,我看到几乎每个人,每本杂志,每个网站,都在推荐。直到最近身边同事谈起这书,我知道自己不能不买一本了。

https://www.amazon.co.jp/dp/4822289605/ref=cm_sw_r_cp_awdb_c_QDgqCb0D73YNJ

《Factfulness》告诉了我们一件事,近几十年全球都发生了哪些巨大的变化,而我们却因为某些傲慢与偏见而不自知。

书从十几道这样的三选一的问题开始:全球低收入国家上完小学的女性有多少?赤贫人口又有多少?我答对了四道,我从事社会科学研究的博士后的夫人也没比我好哪里去。而作者用同样的问题问全球各界精英,结果也是惨不忍睹。比随机选还差。

作者告诉我们,其实世界没有那么糟。虽然我们还在面对很多富具挑战性的全球性问题,但总体来说还是一年比一年好。比如第一题的答案:低收入国家有60%以上的女性能读完小学。不止教育,低收入国家平均年龄达到62岁,大部分儿童都能接种疫苗。

作者的统计显示似乎很多人觉得世界越来越糟。

我自己是没有这种看法的。我是一个乐观主义者。

以前跟夫人聊过一个问题:如果你可以保住目前的社会阶级的前提下穿越时间线,你最想在哪个时代出生?

夫人的答案是现在。原因是,要是在过去,她可能会读不起书,然后莫名其妙的要嫁给不知名的大叔,生儿育女,周而复始,毁掉下一代。

而我的答案是希望晚点出生。在家乡我小时候学计算机只能从图书馆翻那些过时的教科书,而现在google一下任何知识都唾手可得。而我的父母那一代,还有文革,连书都没法读。经历过知识稀缺的时代,才能体会现代网络的可贵。我想很多同龄人看《无敌破坏王2》看到Vanellope从小镇第一次走进网络世界瞪起好奇的大眼睛的时候,也会跟我一样感动流泪。

其实我们都能感受到世界在变好,尤其是中国人,中国人应该算是最能感受到世界在变好的国民之一。

以前在国内的时候感觉不明显,那时的感受是渐变式的;出国后每年一两次的回国感受尤深,那是一种跳跃式的进步。在我小时候中国还是个公认的发展中国家,积贫积弱;现在却是世界第二代经济体,跟美国打贸易战,输出价值观。就算在海外也能感受到这种变化:只要晚上打开电视看WBS,十有八九又是在谈论中国。

其实我们知道世界在变好,只是没想到变好得这么快,而这种变好是全球性的。我们以为只有中国在飞跃,却忽视了全球的成长。细想一下,凭什么只有中国在增长,而地球其他角落却无法不到上帝的眷顾。这毫无道理。

而我居然还以为非洲和印度还有大部分地方赤贫,而中东还在受战乱所害。上网看到难民的新闻,感叹一下第三世界国家真可怜,人类真可悲,然后又回头码代码去。

这是一种傲慢,一千年前中国得过,两百多年前欧美染过,现在又回到了中国。

刘慈欣说,弱小和无知,不是生存的障碍,傲慢才是。

感谢《Factfulness》,不然我死了也不知道什么回事。

最简单的Rust+WASM

最近想写点东西,先写web版,要是跑得好的话再做app版。思来想去Rust应该是最能胜任这个任务:可以port到WASM,跑在web上,也可以port到iOS/Android,性能也非常好。

Rust做WASM有很多方法,官方也有教程介绍如何做。可惜的是这种写法比较底层,只适合学习,而且没有开发模式的热更新,对于快速出原型来说开发效率不高。找了一下最简单的方法应该是用一个叫stdweb的包,非常简单。

首先你要已经装了rustup,记得升级一下。

$ rustup update

加入wasm的target

$ rustup target add wasm32-unknown-unknown

新建项目

$ cargo new mywasm

打开Cargo.toml,加入stdweb依赖:

[package]
name = "mywasm"
version = "0.1.0"
authors = ["Chifung Cheung <chifung.cheung@gmail.com>"]
edition = "2018"

[dependencies]
stdweb = "0.4.13"</code></pre>

打开src/main.rs,加入

#[macro_use]
extern crate stdweb;

fn main() {
    stdweb::initialize();

    let message = "Hello, 世界!";
    js! {
        console.log( @{message} );
    }

    stdweb::event_loop();
}

回到项目底下,开跑:

$ cargo web start --target=wasm32-unknown-unknown

编译完了打开http://localhost:8000,应该就能看到alert了。stdweb支持热更新,虽然体感有点慢就是。