移除 macOS 中被企业策略强制安装的 Chrome 插件扩展的方法

公司配发的 Macbook 安装了一些监控软件,毕竟是办公设备,这点倒是无可厚非。但是,其中的 Forcepoint DLP Endpoint 会向日常使用的主力浏览器 Chrome 中安装一个 WebsenEndpoint 的扩展插件。

这个插件的作用不必多说,但是有一个副作用就是让 Chrome 无法自动填写任何网站用户名密码。每天要输入上百次的密码这种体力活是绝对不能忍受的。于是尝试卸载这个插件。但是这个插件是使用企业策略 (Installed by enterprise policy) 强制安装,无法在Chrome中下载。但是可以通过将与之关联的 Profile 删除实现卸载的作用:

但是过两天,Forcepoint 又会把这个插件装回来😂 不过没关系,我们有的是办法。

方法一:通过占用 Chrome 插件文件位置,防止真正插件重新装回

原理是这样的:Chrome 的插件安装在 ~/Library/Application Support/Google/Chrome/Default/Extensions 目录下:

除去 Temp, 每个文件夹对应一个插件。我们希望移除的插件的文件夹名称是 kmofofmjgmakbbmngpgmehldlaaafnjn。由于 macOS 文件夹下,相同名称的文件和文件夹只能存在一个。因此,我们只需要在插件删除以后,在这个目录下新建一个名称为 kmofofmjgmakbbmngpgmehldlaaafnjn 的空文件:

然后,再通过文件系统的 Lock 功能锁定改文件,防止该文件被 Forcpoint 删除:

至此,大功告成,so easy!

方法二:修改 Forcepoint DLP Endpoint 插件安装脚本,禁止强奸 Chrome

法一虽然可以防止Chrome被重新安装插件,但是过几天以后,你会在你的 Profiles 配置下发现,虽然 Chrome 插件安装失败了,但是依然会成功安装这个 Profile:

虽然不会干扰系统的任何功能,但是估计很多强迫症患者看到这个还是挺难受的……从图中我们可以看到,这个插件的路径在 /Library/Application Support/Websense Endpoint/DLP:

其中 setup_chrome_ext.sh 就是强插 Chrome 的脚本:

脚本写得简单粗暴:通过 $PROFILES -I -F "/Library/Application Support/Websense Endpoint/DLP/WebsenseEndpointExtension.config" 向系统强行安装 profile. 要防止其安装插件,把这行代码删除即可。但是,这个文件是属于 admin 用户组的,我们平时使用的账号,即使集合 sudo 也只是 wheel 用户组,是干不过 admin 的,导致我们无法在正常模式下编辑该文件:

因此,我们需要通过 Command (⌘)-R 重启到恢复模式,然后在 /Volumes/mac.os/Library/Application Support/Websense Endpoint/DLP 目录下,将对应代码删除即可。

至此,自己基本满意了🤔

后记

稍微浏览了一下 /Library/Application Support/Websense Endpoint 文件夹,发现其实这个软件不仅强插 Chrome, 还会强插 FireFox:

艾玛……proxy…吓死宝宝了,已经不想改脚本了。直接在恢复模式下删除文件,然后用方法一的方法,建了一个 Lock 文件锁定这个位置。什么,还有 upgrade.sh? 你怎么不上天呢,也一并删除了……突然想起,自己在无所不能的恢复模式下,要不整个软件一起删除了?想到隔天IT技术小哥可能过来找麻烦,就此打住吧😂

TLS 1.3 当前(2018.10)支持与部署之现状

今年8月10日,历经三年有余,TLS 1.3 最终版本终于得以发布——RFC 8446. 关于 RFC 的详细介绍可以进一步阅读 A Detailed Look at RFC 8446 (a.k.a. TLS 1.3). TLS 1.3 因为其在握手延迟以及安全性上的改进 (可以参考拙文《TLS1.3/QUIC 是怎样做到 0-RTT 的》),毫不夸张的说,这是一件将深刻而长远影响互联网发展的技术里程碑。那么将 TLS 1.3 尽快平滑应用到线上环境无疑是一件势在必行的事情了。

在我日常的工作中,对于 TLS 1.3 的支持和部署主要关注两个层面: 编程语言(Go, Java)、 API gateway (Nginx) 和浏览器. 下面分别介绍一下这三个层面 TLS 1.3 的支持部署现状。(因为 RFC 8446 已经发布,因此本文中说的支持如无特殊说明,都是指对最终版本 RFC 8446 的支持。)

编程语言对 TLS 1.3 的支持

Go 方面,官方在 TLS 1.3 draft 阶段一直没有跟进。因此,有一个关于对 TLS 1.3 支持的 issue 从 2015 年 open 至今都没有关闭:crypto/tls: add support for TLS 1.3. 其实 Go 发布版本和改进标准库的效率还是挺高的,对于 TLS 1.3 上的“不作为”更多是因为 Go 在兼容性上的承诺导致其并适合在最终版发布前实现互不兼容的 draft 方案。

而 Go 1.11 的发布时间(2018.08.24)与 RFC 8446 的发布时间比较接近,没有足够时间实现并发布该特性。从 golang-dev 小组讨论 Re: crypto/tls and TLS 1.3 看,由于 1.11 没有实现 TLS 1.3 ,那么 1.12 实现 TLS 1.3 基本是板上钉钉的事了:

The key scheduling fact is that the Go 1.11 feature freeze is just a week away, so we decided that it would be too rushed to merge the 1.3 patches for it. I definitely aim to have TLS 1.3 in Go 1.12.

根据惯例, Go 1.12 的发布时间将会是 2019.02~03. 如果期间你实在想用 Go 编程测试 TLS 1.3, 可以尝试使用 CloudFlare 的 tls-tris 库。根据 Go net/http 标准库维护者 Brad Fitzpatrick 的消息,这个库将会被合并到标准库作为 Go 官方 TLS 1.3 的实现。因此,如果你不得不用这个库干一些生成环境的活也大可放心,即使日后升级 Go 1.12, 接口兼容性还是有保证的。

Java 方面,由于 Java 11 出生时间好(2018.09.25), 因此是出生就支持 TLS 1.3 RFC 8446, 具体可以参见 JEP 332: Transport Layer Security (TLS) 1.3. Java 11 是 LTS 版本,因此,如果有条件升级到 11, 推荐使用 Java 11 实现的 TLS 1.3 以及配套的 HttpClient;如果生产环境暂无法升级 Java 版本,推荐使用 OkHttp. 关于 Java Http Client 选型可以参见Java HTTP 组件库选型看这篇就够了

Nginx 对 TLS 1.3 的支持

准确讲应该是 Nginx 所使用 SSL lib 对 TLS 1.3 的支持。在这方面,Boring SSL 跟进速度飞快,在 RFC 发布后第4天实现了对最终版本的支持。OpenSSL 虽然很早就跟进了 draft 的实现,但是对最终版本的支持需要 1.1.1-pre9 及以后的版本:

The OpenSSL git master branch (and the 1.1.1-pre9 beta version) contain our development TLSv1.3 code which is based on the final version of RFC8446 and can be used for testing purposes (i.e. it is not for production use). Earlier beta versions of OpenSSL 1.1.1 implemented draft versions of the standard. Those versions contained the macro TLS1_3_VERSION_DRAFT_TXT in the tls1.h header file which identified the specific draft version that was implemented. This macro has been removed from 1.1.1-pre9 and the current master branch.

TLSv1.3 is enabled by default in the latest development versions (there is no need to explicitly enable it). To disable it at compile time you must use the “no-tls1_3” option to “config” or “Configure”.

Although the latest 1.1.1 versions support the final standard version, other applications that support TLSv1.3 may still be using older draft versions. This is a common source of interoperability problems. If two peers supporting different TLSv1.3 draft versions attempt to communicate then they will fall back to TLSv1.2.

而第一个 OpenSSL 1.1.1 release 是在 2018.09.11, 因此如果你跟我一样是 OpenSSL 的死忠粉,当前阶段 Nginx 支持 TSL 1.3 的最佳方式是 Nginx 1.15.5 + OpenSSL 1.1.1. 而这种脏活、苦活、累活当然是交给 Docker 解决了:从源代码编译 nginx docker 镜像开启 TLS 1.3,项目地址可以参见 docker-nginx.

配置 Nginx 支持 TLS 1.3 需要注意一点:默认情况下 Nginx 因为安全原因,没有开启 TLS 1.3 0-RTT,可以通过添加 ssl_early_data on; 指令开启 0-RTT. 完整配置可以参考 nginx.conf.

浏览器对 TLS 1.3 的支持

当前阶段,Chrome 69 和 Firefox 62 都只支持到 draft 28, 而 draft 28 与最终版本是不兼容的。因此,要测试体验 TLS 1.3 final 需要使用 Chrome Beta 测试版。然后在 chrome://flags/#tls13-variant 开启 TLS 1.3 final:

扩展阅读

基于 Docker 搭建 Mac 本地 HBase 环境

说起玩大数据,相信很多人都会因为 Apache 全家桶软件配置而菊花一紧。Docker 的出现,把很多玩大数据就是配机器、配环境的开发者从泥潭中拯救了出来,虽然还不能完全替代线上环境,但是在开发环境,无疑为开发者节约了大量搭建本地环境的时间。比较遗憾的是,我们团队之前也是没有独立的数据测试环境😅,于是把在本地搭建 HBase 环境整理和记录如下。

系统环境:

  • MacBook Pro (Retina, 15-inch, Mid 2015)
  • 2.2 GHz Intel Core i7
  • 16 GB 1600 MHz DDR3
  • macOS 10.13.6

安装 Docker CE for Mac

Docker Community Edition for Mac下载安装。

Mac 上的 Docker 环境经过 docker-machine/virtualbox 几次变化,如今的 Docker CE 已经支持原生 Mac 环境,因此当前阶段 Docker CE for Mac 就是唯一推荐的 Mac Docker 环境,再也不用通过安装 virtualbox 这种借蛋生鸡的方式了,实在是很赞。此外,现在的 Docker CE 集成了 Kubernetes, 因此本地玩 k8s 也不需要额外进行安装配置。如果你计划以后就是玩 k8s, 那么你以前安装的 Kitematic 也可以卸载掉了。Kitematic 除了一个图形化的 container 管理界面,实在没有什么值得留恋的,因此官方停止其开发无疑是个正确的决定。

获取和启动 HBase Docker 镜像

  • 获取容器镜像

更多大数据全家桶 Docker 镜像可以参见 HariSekhon/Dockerfiles

  • 启动容器

参数解释:

  • -d: 后台启动。
  • -h: 容器主机名,必须设置该项并配置 hosts,否则无法连通容器。
  • -p: 网络端口映射,这里只把要使用的端口(zookeeper端口、HBase Master端口、HBase RegionServer端口等)映射了出来,你可以根据自己需要进行端口映射。常用 HBase端口可以参见下表:

但是,harisekhon/hbase 修改了默认端口:

因此,你看到启动参数中的端口参数是那样的。

  • --name: 容器别名。

设置hosts (推荐使用 Gas Mask):

成功启动后,就可以在 http://localhost:16010/master-status 查看 HBase 状态了:

需要注意的一点是:容器销毁后,数据也也会被同时销毁。因此你可以通过 -v YOUR_DIR:/hbase-data 的方式将数据目录映射到宿主机目录,防止数据丢失。

编写测试代码

  • 创建 table

  • 读写 table

Maven 添加依赖:

HelloHBase.java:

扩展阅读

Java HTTP 组件库选型看这篇就够了

Java HTTP 组件库选型看这篇就够了

最近项目需要使用 Java 重度调用 HTTP API 接口,于是想着封装一个团队公用的 HTTP client lib. 这个库需要支持以下特性:

  1. 连接池管理,包括连接创建和超时、空闲连接数控制、每个 host 的连接数配置等。基本上,我们想要一个 go HTTP 标准库自带的连接池管理功能。
  2. 域名解析控制。因为调用量会比较大,因此希望在域名解析这一层做一个调用端可控的负载均衡,同时可以对每个服务器 IP 进行失败率统计和健康度检查。
  3. Form/JSON 调用支持良好。
  4. 支持同步和异步调用。

在 Java 生态中,虽然有数不清的 HTTP client lib 组件库,但是大体可以分为这三类:

  1. JDK 自带的 HttpURLConnection 标准库;
  2. Apache HttpComponents HttpClient, 以及基于该库的 wrapper, 如 Unirest.
  3. 非基于 Apache HttpComponents HttpClient, 大量重写应用层代码的 HTTP client 组件库,典型代表是 OkHttp.

HttpURLConnection

使用 HttpURLConnection 发起 HTTP 请求最大的优点是不需要引入额外的依赖,但是使用起来非常繁琐,也缺乏连接池管理、域名机械控制等特性支持。以发起一个 HTTP POST 请求为例:

可以看到,使用 HttpURLConnection 发起 HTTP 请求是比较原始(low level)的,基本上你可以理解为它就是对网络栈传输层(HTTP 一般为 TCP,HTTP over QUIC 是 UDP)进行了一次浅层次的封装,操作原语就是在打开的连接上面写请求 request 与读响应 response. 而且 HttpURLConnection 无法支持 HTTP/2. 显然,官方是知道这些问题的,因此在 Java 9 中,官方在标准库中引入了一个 high level、支持 HTTP/2 的 HttpClient. 这个库的接口封装就非常主流到位了,发起一个简单的 POST 请求:

封装的最大特点是链式调用非常顺滑,支持连接管理等特性。但是这个库只能在 Java 9 及以后的版本使用,Java 9 和 Java 10 并不是 LTS 维护版本,而接下来的 Java 11 LTS 要在2018.09.25发布,应用到线上还需要等待一段时间。因此,虽然挺喜欢这个自带标准库(毕竟可以不引入三方依赖),但当前是无法在生产环境使用的。

Apache HttpComponents HttpClient

Apache HttpComponents HttpClient 的前身是 Apache Commons HttpClient, 但是 Apache Commons HttpClient 已经停止开发,如果你还在使用它,请切换到 Apache HttpComponents HttpClient 上来。

Apache HttpComponents HttpClient 支持的特性非常丰富,完全覆盖我们的需求,使用起来也非常顺手:

对 Client 细致的配置和自定义支持也是非常到位的:

完整示例请参考 ClientConfiguration.

基本上,在 Java 原生标准库不给力的情况下,Apache HttpComponents HttpClient 是最佳的 HTTP Client library 选择。但这个库当前还不支持 HTTP/2,支持 HTTP/2 的版本还处于 beta 阶段(2018.09.23),因此并不适合用于 Android APP 中使用。

OkHttp

由于当前 Apache HttpComponents HttpClient 版本并不支持 HTTP/2, 而 HTTP/2 对于移动客户端而言,无论是从握手延迟、响应延迟,还是资源开销看都有相当吸引力。因此这就给了高层次封装且支持 HTTP/2 的 http client lib 足够的生存空间。其中最典型的要数OkHttp.

OkHttp 接口设计友好,支持 HTTP/2,并且在弱网和无网环境下有自动检测和恢复机制,因此,是当前 Android APP 开发中使用最广泛的 HTTP clilent lib 之一。

另一方面,OkHttp 提供的接口与 Java 9 中 HttpClint 接口比较类似 (严格讲,应该是 Java 9 借鉴了 OkHttp 等开源库的接口设计?),因此,对于喜欢减少依赖,钟情于原生标准库的开发者来说,在 Java 11 中,从 OkHttp 切换到标准库是相对容易的。因此,以 OkHttp 为代表的 http 库以后的使用场景可能会被蚕食一部分。

小结

  • HttpURLConnection 封装层次太低,并且支持特性太少,不建议在项目中使用。除非你的确不想引入第三方 HTTP 依赖(如减少包大小、目标环境不提供三方库支持等)。
  • Java 9 中引入的 HttpClient,封装层次和支持特性都不错。但是因为 Java 版本的原因,应用场景还十分有限,建议观望一段时间再考虑在线上使用。
  • 如果你不需要 HTTP/2特性,Apache HttpComponents HttpClient 是你的最佳选择,比如在服务器之间的 HTTP 调用。否则,请使用 OkHttp, 如 Android 开发。

扩展阅读

炎热焦灼的八月

显然,看到这个题目的时候,说明拖延症又犯了。

这个月天气炎热,个人也进入了一个相对焦灼的状态。从心里深刻的知道这不是一件坏事,只是当前面临的问题和挑战是需要去直面并迅速解决。

这个月最大的感慨是在「专注」上做得不够。导致很多事情都在上下文切换的过程中被肢解的支离破碎,完全没有了初心,当然离预期也就相去甚远了。

与之关联的另一个做得不好的点是「效率」。在思维体力上,自我感觉已经进入一个相对强韧的阶段,但是在解题效率上依然让自己常有隐忧。今天在路上,看了达里奥《原则》的个人生活部分。挺有启发的,他讲个人生活愿景的追求实现划分成5个阶段,每个阶段有给出了不同的原则。自我对照检查,我觉得自己在认清问题本质以及规划上面是存在严重问题的。导致问题主体识别出现偏差或错误,而规划上又没有达到完全开放大脑的状态,因此,无论是在视角高度还是做事深度上都深陷泥潭。

这个月尝试换一种心态和姿态与最爱的人相处。虽然依然做不到大脑完全开放的状态,但是意外的发现其实当我尝试这样去做的时候,至少我已经放下潜意识中的自我,不在用这种潜意识做一些理所当然的决定,然后筛选证据,去印证满足自己内心的欲望和幻想。妞儿给了自己机会让自己开了一个好头,期待自己后面的改变。

本月推荐的电影当然是《蚁人2》,因为这是这个月看过的唯一一部电影。适度的夸张而不荒谬,沉重的剧情总是用暖心的美式幽默裹挟,彩蛋中巧妙的与复联结合,这三点应该是本片让自己最喜欢的点。如果你还没去电影院,推荐你去看看。

本月推荐的书毫无疑问是《原则》。应该说这本书红黑的精装版本在很大程度上是可以让你在公共场所作为小资的装X读物的。但是,我觉得这本书的很多书评的确有点过誉这本书了。书的确不错,不过作者依然没有能够摆脱为了写一句话而堆砌一个章节的问题。并且,本质上,这其实就是大家口中最正宗的心灵鸡汤类读物。我并不反对心灵鸡汤,我只是很难理解为什么很多人总是向你表达自己不屑于心灵鸡汤读物的同时却认为《原则》是一本高大上的书。

就我个人而言,《原则》毫无疑问是一本让我受益匪浅的书。它让我自查出了很多自己以前认为理所当然的点,虽然我不一定完全同意其建议的原则和行事方式。但是,的确让我开始重新思考和认识一些事情。这与之前看到的陆奇给后辈的原则其实是相互呼应的:陆奇成功背后的故事:决定人生高度的,是这 7 条原则
。陆奇的给出的这些原则非常具体,但是做起来非常不易,摘录如下,以自勉:

  • 坚守价值观(坚持做对的事情)
  • 永远正能量
  • 高度自律
  • 每天学习
  • 把公司的事当成自己的事
  • 从我做起
  • 谦逊真诚

明天会认识很多优秀的新同学,带着小小的焦虑感,但一下能结识如此多有意思的灵魂,依然非常期待。

个税起征点调整到5000可能只是一针生理盐水

8月最后一个工作日,听闻我朝要将个税起征点调整至5000元人民币。本以为是一针兴奋剂,即使是短效的;仔细看了一下,却发现可能只是一针生理盐水。

中国修改个人所得税法的决定经十三届全国人大常委会第五次会议表决通过,起征点确定为每月5,000元人民币,新个税法于2019年1月1日起施行,2018年10月1日起施行最新起征点和税率。关于个税征收方面,央视新闻的这张图解释非常清楚:

变化1、3都是可以直接数字量化,这部分的确可以让大家立即尝到这次个税修改的甜头。而让大家想象空间很大的变化2因为缺乏具体实施的细则,这里不做任何揣测。

但是,这些变化其实不是什么国家发红包。这只是我国个税例行的周期性调整修改。之前两次调整分别是:2005年调整起征点到1600元;2011年,调整个税起征点至3500元。这次从3500调整到5000元,无论是调整周期还是调整幅度其实是远不如2011年的。这种情况一方面与个税调整是以增加税收为目标相吻合,另一方面也反映出当前经济压力。

你没有看错,个税调整是以增加税收为目标的。而这次个税调整造成的个税总额暂时性降低可能到不到一年就能逆转过来。

而这次调整被大家吐槽最多的可能就是税务局以后将负责征收社保。之所以被吐槽,是因为以前社保局负责征收社保的时候因为没有与收入挂钩,因此很多企业按照最低标准为员工缴纳社保。由税务局负责征收以后,这个中间的信息壁垒不复存在,企业只能够按照实际公司为员工缴纳社保。带来的后果就是企业的用人成本上升。

社保bug的修复所造成的影响可能比想象的大。这个bug不影响国企、央企、大厂,因为这些企业基本都是紧跟政策走。而对小微企业的影响就不可小觑了。在当前的经济压力背景下,这其实是对这类企业的征税(虽然缴纳社保并不算征税,但是哪个企业敢不给员工缴纳社保呢?)。可能员工觉得虽然社保会多交一点,但是日后可以领呀(暂且不论你退休的时候社保这个击鼓传花还能不能传下去……),企业主烦恼就让他们烦恼吧。短期看也许是这样的,但是在社会分工如此细化的今天,很多环节都是会联动受影响的。企业用人成本的增加最终一定会反映在整个经济层面。而具体到雇佣层面,要么会有更多的小微企业在明年关门大吉,要么企业主减少能提供的工作岗位数量。

从个税征收总额数据看,我国过去几年的个税都保持在10%以上的增长,是GPD增速的两倍以上。这次调整其实更多的是稳住税基,同时进一步巩固征税成本低的中等收入人群。在这次调整的细则中也可以应证这一点:草案中,将工资薪金、劳务报酬、稿酬和特许权使用费四项劳动性所得实行综合征税,目的是实施”按人征税“。此番修改的本意,是认为拥有多项收入来源的人士在现行税制下享受了更多的减免优惠,如果将多个收入来源综合起来征税,就会更合理,真正起到调节收入的作用。

我们再看一组财政部2017年财政收人情况数据:

从去年开始,你可能没少听说要为企业减免税负的的消息。但是增值税、消费税、企业所得税的实际数据可能让你失望了。所以,从整体上开,财政部这套班子的思路前后还是非常一致的🤔。而这次的个税调整,其实是去年开始实行的去地税化改革一揽子计划措施的一部分罢了。从这点看,税务局以后会兼任更多的职能和现金流入口。

总体来看这次个税改革喜忧参半,毕竟不能所有的好处都让P民给占了。我们看一下上述后面的税收大头:车辆购置税今年上半年宣布的减免政策估计大部分人都当成不存在了;进出口税在贸易战的当口根本指望不上。那么这次的个税改革可能代表了后续更多的系列财政举措,这个改革可能连一个国家红包都算不上,只是给你象征性的打了一针生理盐水,肯定打不死人,但是会让大部分自嗨的如同一支兴奋剂。

谈谈我认识的优秀的人和有趣的灵魂(二)

上周简单谈了谈认识的几个典型而有意思的人:谈谈我认识的优秀的人和有趣的灵魂。篇幅原因,还有很多有有趣的人没能记录下来。今天算是上一篇的姊妹篇,会谈更多的人,隐私关系,不会说的太细,但是人物的维度会更丰富。

大洋洲的 Bimmer

最开始关注到王同学是因为一个共同的兴趣:车,一个算不上高雅,稍微世俗的爱好。有人说,男人只不过是年龄更大的男孩,从某种意义上说是对的:很多男生的兴趣爱好从小到大始终是离不开枪、车、球。很不幸,在这点上,我跟王同学双双中枪。

因为跟王同学有很多趋同的背景和观点:IT男,喜欢驾驶,Bimmer,因此对其有一种天然的无距离感。王同学爱车,爱 BMW 在中文推特圈是非常有名的。毫不夸张的说,如果谁想咨询了解 BMW 任意车款,这个圈子很多人第一个想到的 Bimmer 一定是王同学。前不久,王同学购入了让人口水的 M2, 所以你丝毫不用担心其是否是键盘车神。

然而,王同学真正让自己印象深刻的是其强烈而明确的知道自己想要什么。M2 当然算是一个,但是远不是他的全部。因为都是 IT 从业者,因此特别关注过他的职业生涯。从移民大洋洲,到大洋洲非常「幸运」的前后加入若干家上市/被上市公司收购公司来看,履历是非常精彩的。在我认识的人中,他的履历还算不上世俗上的「成功」,但是,他的每一步都是如此清晰和确定。在王同学这里,上帝是不掷骰子的。

王同学是那种真正做到 work hard, play hard 的人。做任何事情都非常认真,让时间在任何事情上面流转的质量都很高。这是很多人都明白的道理,但是真正做到的人很少。

宝马东与迈巴赫东

作为一名肉身在天朝的 IT 从业者,几乎每天都要从事一项无形存在的网络运动。如果你从事这项运动的时间足够长,你应该有很大概率知道东哥。咳咳,不是京东那个娶了你们奶茶妹妹的东哥😅

在自己的职业生涯中,有过一次 gap season, 期间做的一些事情顺藤摸瓜摸到了东哥的推特。意外的发现,东哥也在成都,于是有了面基的机会。但是,东哥其实是一个比自己想象要有内容、经历要丰富的人:一个传统教育上的坏孩子,然后靠着勤勉、聪明和韧劲,活出了自己的野路子。除了网络相关,还在工地搬过砖,搞过旅行社,现在是本地一家硬件解决方案的大佬,经常介绍的项目单子都是千万起,诸位感受一下。

东哥聊起豪车来举重若轻,尤其是宝马和马巴赫。一般他的跟随者只能在他后面回复道:这种车我居然也敢点进来,看来我是膨胀了。

很多时候,村里的孩子找我聊一些人生困惑的时候,我都跟他们将东哥的故事。学历、家境和大环境都只是影响因素,但万万不能成为你的接口,至少,只要你手脚足够勤快,并且愿意动脑子,这个社会是饿不死人的。

拍拍屁股就下班的老专家

已经忘了最开始为何关注老专家了。后来印象深刻的却是因为老专家每天下班前都会发一张美臀照😂 (这一点跟冯大辉 Fenng 倒是挺类似的)。不过老专家的本事可不止发掉屁股照,而是一个资深的职场老司机。

可能很多技术出身的人会条件反射般的反感这种人,但是在我看来,其实他让我看到了职场的另一面。并不是说老专家一路上位是如何擅长玩弄权术,而是让很多人能够相对理性的看待真实的职场。

老专家经常挂嘴边的一句话是,如果这点自制力都没有,还攀登个屁的人生巅峰。这点自己感受挺深刻的。因为每当他发这句话的时候,我都还在肆无忌惮的刷手机或者消遣时间。看到他这句话如同挨了一耳光,还是继续刷手机和消遣时间😅

其实,任何人风骚都是建立在某种支撑上的。要靠先天和智力方面的优势全面碾压已经越来越不容易做到了。老专家当然是明白人,虽然平时说话风骚务必,但是其实每天拍拍屁股下班都是深夜;虽然平时拿股票奖金跟喝水一样,但是也会在遇到问题隐隐的焦虑和事情推动不顺利时的愤怒。什么时候老专家下班时候不在拍屁股了,我想那可能他真的有点累了。

怀揣互联网之心的央企许主任

有时候会尝试面试打靶,倒不是要换工作,而是了解一下市场最近技术和人才的风向标,同时聊一些人,且有很大概率结识一些有趣的人。许主任就是其中一个。

许主任就任于某央企,年轻骨干,距离下一步晋升部长就差临门一脚。阴差阳错,他们拿到了我的简历,让我过去聊聊。央企的背景让我还没去就闻到了一股浓浓的体制味道。因此聊天意愿并不强烈。但是发现他们公司离家很近,打算在上班前去聊聊,然后快速走人,即不影响上班,也算是礼貌地走一下流程。

一路聊下来,前面的小兵小将让我更加确信自己的判断。但是聊到许主任的时候,我的观点发生了一点变化,虽然对这家央企依然不感兴趣,但是对眼前这个人内心是发亮的,因此我们聊了一个上午。

以我对央企体制的了解,许主任是不应该对技术有如此深入和细致的研究的,这是在进行一场常规意义上面试时,其最初吸引我的地方。另一方面,他跟我介绍了其在部门进行互联网一些模式的尝试和探索,并非常坦诚的跟我沟通了这个地方的天花板和发展前景,自己的规划和面临的困难,以及对我的一些建议。其实这些聊天内容跟面试已经没有关系,更像是朋友之间的聊天。如果是去之前我认为央企就是一滩死水,但是遇到许主任以后,我知道了其实每个组织都有认真做事的聪明人,而一般这种大型的组织都是复杂的,你需要识别出其中有意思的个体。

后来当然是没有去许主任那里,但是就在我快忘了这件事的时候。许主任给我打了一通电话,并不是来谈条件,只是说这是他的联系方式,后面可以常聊天。

套路迥异的李哥

打靶认识的另一个人是李哥。应该说这次常规意义上的聊天是没有达到预期的,可能 HR 错误传达了简历中的某些内容吧。好在我在现场跟李哥进行了沟通,消除了了这种误会。

李哥已经是他们公司的元老,办公室内一排(注意是一排)整齐的咖啡机,以及一堆凌乱的硬件,倒还挺有反差的小资和科技美感。李哥很疑惑的说,你中午没吃饭就过来,要是HR这么安排我,我才不来呢……你现在的公司时间点非常好,要是我才不动呢……卧槽,打靶无数,第一次见到这种让人不要考虑这个机会的套路的人……就在我狐疑和懵逼的时候,李哥直接说去下面吃饭吧,我想反正吃谁不是吃呀,跟着下去了。

饭桌上的聊天跟职位就没有关系了。聊了聊生活、成都的圈子,以及最近资本的动向。了解到了李哥挺多的背景和经历,心里默念牛x,路上加了微信。日后也联系和关注着。李哥其实已经渐入中年,他展示了可能自己十年后一个可能的状态。但是现在的李哥是非常快乐的,虽然行事完全不遵从公司套路,办公室就是他的试验场,改落地和执行的事情一个也没少。按照李哥的话说,及时下午3点下班,那一定是心安的。李哥自称资深球迷,但是预测比分的水平堪忧(估计今年世界杯赔了不少钱)。他跟我提起过他对其现在生活的感受:去其他公司的可能性不大了,也会被人歧视;我也不喜欢现在的公司,但是在这里,他还有他要去完成的使命。


还有很多人,各种原因,不太适合写出来,比如混圈子时候认识的昊哥和涛哥,一个永远能给出你满意答案的前辈,一个在组织内遵从内心认真做事并不惜在一定程度是与组织对抗的见贤思齐的同龄人。其实我深刻的明白,每个人因为自身价值体系和背景的不同,对同一个人的感受是千差万别,甚至是完全相反的。我记录的这些人未必是你的体系中的有趣的人,但是这没有关系,至少有几点是具有共性的:

  • 你结识人的路径很大程度上决定了你身边都是怎样的人。而你身边的这些人基本上能从各个维度反映你当前的状态。
  • 人没有绝对的有点与缺点,多关注和了解一个人的特点。
  • 读万卷书,行万里路,路途本身是无聊的,一定是路上你之前认识或不认识的人。

谈谈我认识的优秀的人和有趣的灵魂

这两年关注的事情不太聚焦(坦率讲,应该是非常发散😂),因此有意无意地接触了非常多的人。对于我这种不太擅长社交的人来说,最开始干这种事的时候是一百个不愿意的,并且喜欢用「无意义社交就是浪费时间」来宽慰自己。必须承认,这种有点混圈子嫌疑的社交的确利弊参半,但是,如果你要问对这些「弊端」是否后悔,我的回答一定是否定了。其中最重要的一个原因是,认识了一众优秀的人,和一众有趣的灵魂。

为了避免造成不必要的麻烦和误解,下面提到的人我都尽量模糊化处理。人物的顺序按照我的思路出现,比较随机,不分先后。

我眼中的“华阳乔布斯”

任何时候,评论前东家老板其实都是挺忌讳的事情。一方面,可能这个员工当初离开的时候受了小委屈;另一方面这个老板可能已经在公司制度层面要求将这些离职员工纳入招聘黑名单了。如果你在成都某游戏公司工作过,你大概率可以一次把这两条一次集齐。

但是,请别误会我。虽然一个公司的文化必然受创始人的价值观影响,但是如果你能够跳出当时的那种雇佣关系,毫无分别心的看这个人的过往和经历,往往能够对他有不一样的认识和看法。显然,前老板“华阳乔布斯”(后简称乔)是让我意识到这一点的人。

乔因为极度推崇、学习和实践苹果乔布斯而得名。举几个例子:乔如果看到某个邮件存在问题,他会在全体邮件、公司大群中直接怼人,并在末尾附上一句「建议辞退该员工」;乔如果对项目不满意,他会直接过来跟执行人直接对骂,甚至……干架;要进入 t 公司也不容易,首先要测智商,“低能儿”不要,通过所有面试以后,还有一场面向公司高管的入职演讲,这可不是做样子,是真正的大概率刷人的;离开 t 公司,会上公司招聘黑名单……

我以前对乔的这些做法挺困惑的,还在 t 公司那会儿,一次跟领导聊道,乔这样行事,是不是你们这群高管惯出来的巨婴?领导稍微迟疑了一会儿,说,他虽然年长乔好几岁,但是内心对乔的心怀尊敬的,然后他谈了公司当初是如何在乔的带领下成长和如何克服困难的。我当时没有接话,陷入了尬聊。如今想来,很多人都会当局者迷,掉入屁股决定脑袋的陷阱,如果当时我对领导的回答是狐疑的,那么,现在想来,我相信其是坦诚的。

2016年以后,手游行业逐渐成为了几个寡头的后花园,手游之都,一夜之间,倒下数百家游戏公司。而 t 公司在乔的带领下却离上市越来越近。当然,去年上市答辩临门一脚的时候遇到了挫折,导致 t 当前还未上市,乔应该还是有失落的。但无论是在 t 公司还是离开,我对乔在大方向上的判断以及具体的执行是认可的。而显然,有时候事情的落地不会让所有人都准备好,更不可能让所有员工都满意。

离职以后,虽然上了“黑名单”,但是一直关注乔的朋友圈。隐私关系,这里就不透露他朋友圈内容了。因为已经没有了分别心,看他的朋友圈反而觉得有了点意思:一个财务自由、公司健康运转(至少看起来是)的CEO,勤奋、聪明、好学,时不时还能分享一些有趣的体验和见闻,脾气可能不算好,但是并不让人生厌。对了,离职最后一天的最后一个小时,还听了他的《穷查理宝典》读书分享会,印象中,那个分享会还是要买门票才能去听的。

这两年,自己也在认知上不断的刷新和挑战自己。虽然成长背景、认知路径完全不一样,但是在很多关键问题的认知和判断上,跟乔越来越相似。但是,有一点显著不同,乔能够支配的资源远不是我辈可及,因此,很多时候不仅仅是看其人,也在看他所做的事情来验证自己的思考和判断。

什么?你说你想听乔的花边新闻?算了吧,花边只是浪费你我的时间,人都是多元和复杂的,一个人哪有什么绝对的优点和缺点,关注他的特点就行了。

冯医生的氪金手术刀

坦率讲,我与冯医生并不算熟识。唯一的交流渠道截止目前为止也仅限于推特。但是,了解一个人有很多方法:如果是一个上推的人,你看看他过往的文字就可以了。

最开始关注冯医生是因为推友转发了其一例手术的总结。处于刻奇,关注了冯医生,也翻看了他历史 feed. 没看几分钟,我发现我应该找到了中国最有意思的外科医生(之一)。精湛的手术技艺经常被推友调侃为“冯医生到处给人割肾”;会给求助的推友一些中肯的诊疗建议,但是从来不懂装懂;喜欢摄影、电影、爱折腾网络,虽然非相关专业科班出身,但是逻辑和理解力极好,往往能在推友三言两语的线索中自我解决问题;有两个女儿,与我教育的观念非常契合,经常会分享一些非常具体的子女教育的实操和感悟;身在体制内,热爱自己的职业,同时也非常清楚边界和天花板,然后去贵州开了医院……

冯医生在体制内的时候,就是你能想象到的那种韩剧中才有的偶像医生:长得帅、专业能力突出、收入丰厚。前两条不需要解释,对于最后一条,在中国这么提一个医生容易引起误解,我这里稍微解释一下。冯医生能做到这样,并不是靠患者的红包,更不是跟医药代表勾结,而是因为其在行业的影响力和口碑,使得其有工资薪酬之外的收入,最典型的诸如可以在重庆片区可以开飞刀。而这些都是可以见阳光的。

冯医生大概年长我十岁,但是看他的文字,完全没有代沟,对新事物的认知和接受速度有过之无不及。有一段时间,冯医生摆弄医院进口的达芬奇机器人,遇到一个视频录制问题,然后在推特上求助。我不是这方面的专家,自然也就没有给不靠谱的答案,但是我顺着其他人的回答看了一下,冯医生最终用不到1000块采购的设备解决了该问题。要知道,冯医生旁边的医院解决这个问题可是花了20W+! 总听老一辈说知识改变命运,其实更多时候是见识和认知。冯医生只是一个医学博士,但是专业的训练对其影响是深远的,单凡你跟他对过话,你都能深刻感受到其背后的思考以及其透出的磁力,这种磁力有时候吸引到的是你的注意力和思考,有时候是他要寻找问题的答案。

今年年初,冯医生宣布自己辞职,前往贵州创业。我是创过业的人,深刻的明白创业所面临的问题和风险。不要误解我,我说的「深刻明白」并不是简单感慨“创业维艰,且行且珍惜”,而是我相对来说能更大概率判断其创业方向是否靠谱以及创业者不失败的概率。对于冯医生,从其宣布消息开始,我一直都在默默祝福他。并不是那种廉价的客套话。而是每次看他发布关于新医院建设的问题也好、困惑也好、求助也好,都有一众人提供以各种方式提供帮助、建议和支持。在看到冯医生在院长这个方向上迅速成长后,我也看到越来越多的人在询问能否加入新的医院。最有意思的是,冯医生周末会会重庆,一次请以前医院同事吃饭,结果,几乎整个科室都出来了……有时候,你甚至都没有跟这个人见过面,但是他的所作所为就是让你非常确信他要做的事情可以落地达成;有时候,一个人可能并没有强得那么绝对,但是他的见识领先了一步,做事上靠谱、nice,那么他大概率会成为那个被大家自然托起的人。

有时我跟妞儿讲,这个优秀的人其实就在成都旁边的重庆,得空的时候,也许我们可以找他一起吃顿火锅。

英伦归来的处女座赵教授

作为算是在天朝学术圈混迹过的人,对我朝科研的现状(计算机科学方向)是非常失望的。但是,你如果要说这个圈子没有优秀和认真做事的人,我是一百个不同意的。赵教授并不是我的导师,而是我的导师邀来做过一次学术报告的学者。因此,我与赵教授其实只有一面之缘。

那次交流,赵教授先是做了一个云计算相关的学术报告,由于时间比较充裕,后续自由讨论和交流了一些问题。我被赵教授吸引除了其丰硕的研究成果,还有其对时政的敢怒敢言。而这种敢怒敢言并不是浅薄的愤青,而是实际去参政议政。前两年,赵教授拿到了广东省的五一劳动奖章和五四杰出青年,我知道这不是虚妄,而是实至名归。广东省的数字化建设和政务水平能够领先国内其他地区,以赵教授为代表的这一群人的努力是分不开的。

赵教授是我认识的时间管理做得最好的人。工作上,其在广东最好的两所高校任职,同时积极参政,还是三个孩子的父亲。但是,我发现其实赵教授从来都是举重若轻。什么抱怨、工作家庭矛盾,不存在的,赵教授还可以做更多的事情!而能做到这样,而且在每个方面都游刃有余,除了基本的聪明以外,其实是其经常自嘲的那种处女座疯子版的严格与认真。我没有看到赵教授有过一个完整的周末,也没看到过其晚上不工作。经常看到的是,以小时为单位,在学校、实验室、家、机场、会场、体育馆之间快速的切换。我们经常说,生命的长度是一定的,但是可以选择宽度。我只能说,同学,你还是太年轻了,可能你选择出来的那点宽度比别人的地下室都狭窄。

如果你有机会去广东最好的高校求学,报考的也是计算机方向的专业,并且你看到一个赵姓的教授还有名额,并且认为自己也有变得优秀的潜质的话,毫无保留的推荐你选赵教授。


每当我跟妞儿讲,遇到优秀的人的时候,我内心的喜悦和对对方的喜爱是难以言说的时候,妞儿都会默默的扔下一句“你又没他们优秀”,然后决绝而去😂。要说认识这些自己欣赏的人完全没有功利心,那是不客观的。渐渐地,认识和接触的人多了,放平了心态,毫无分别心的看他们的时候,总能发现这个自己欣赏的群体的一些共性:

  • 独立思考构建的独一无二的认知。这种认知千差万别,你根本不用指望你能遇到一个跟你认知一样的人。识别出你们认知能共振的频段,珍惜交流的机会和质量即可。
  • 清晰的逻辑和极强的理解力。跟行业以及这个人的出生背景没有关系,直观感受就是他能够把他那个领域的问题很快跟你讲明白,同时他也能在很短时间内,提出挑战你自己领域的问题。
  • 严格的逻辑自恰。你也许有很多结论和判断与其是相反的,但是他一定会告诉你得出这个结论的原因和推理依据。
  • 极强的时间观念和严格的时间管理执行。其实生命的长度一样是骗人的,因为单位时间的时间运转效率差异实在是太大了。
  • 演讲力和号召力。不是政客那种带煽动和目的性太强的演讲号召。而是,因为内心有所表达,自然而然的会站到演讲台那个位置。而这种演讲往往其实没有功利心,反而可以被他人托举。
  • 喜欢阅读和写作。世界上为数不多的只有好处没有坏处的事,在这群人身上体现的尤其明显。我并不反感小资,但要说明的是,小资的阅读和写作是不在这个范畴的。
  • 好奇且富有同情心。对这个世界不敏感的人,难以有发自内心的同情心。

这个世界很大,光鲜的皮囊多,有趣的灵魂少;如果你遇到一个,请感谢时间的邂逅。

Go 中如何准确地判断和识别各种网络错误

Go 自带的网络标准库可能让很多第一次使用它的人感慨,这个库让网络编程的门槛低到了令人发指的地步。然而,封装层次与开发人员的可控性往往是矛盾的。Go 的网络库封装程度算是一个不错的折衷,绝大部分时候,我们只需要调用 Dial, Read, Write Close 几个基本操作就可以了。

但是,网络是复杂的。我们有时候需要细致的处理网络中的各种错误,根据不同的错误进行不同的处理。比如我们遇到一个网络错误时,需要区分这个错误是因为无法解析 host ip, 还是 TCP 无法建立连接,亦或是读写超时。一开始的时候,我们的写法可能是这样的:

    errString := err.Error()
    fmt.Println(errString)
    switch {
    case strings.Contains(errString, "timeout"):
        fmt.Println("Timeout")
    case strings.Contains(errString, "no such host"):
        fmt.Println("Unknown host")
    case strings.Contains(errString, "connection refused"):
        fmt.Println("Connection refused")
    default:
        fmt.Printf("Unknown error:%s", errString)
    }

这种根据错误信息进行字符串匹配进行判断的方法有非常明显的局限性:该错误信息依赖于操作系统,不同的操作系统对于同一错误返回的字符串信息可能是不同的。因此,这种判断网络错误类型的方法是不可靠的。那么有没有一种准确而可靠的判断各种网络错误的方式呢?答案是肯定的。

我们知道在 Go 中,error 是一个内建的 interface 类型:

type error interface {
        Error() string
}

要准确判断不同的错误类型,我们只需要类型断言出其错误类型即可。

在 Go 的网络标准库中,错误类型被统一封装为 net.Errorinterface 类型:

type Error interface {
        error
        Timeout() bool   // Is the error a timeout?
        Temporary() bool // Is the error temporary?
}

net.Error 类型的具体 concrete 类型又被封装为 net.OpError 类型:

type OpError struct {
        // Op is the operation which caused the error, such as
        // "dial", "read" or "write".
        Op string

        // Net is the network type on which this error occurred,
        // such as "tcp" or "udp6".
        Net string

        // For operations involving a remote network connection, like
        // Dial, Read, or Write, Source is the corresponding local
        // network address.
        Source Addr

        // Addr is the network address for which this error occurred.
        // For local operations, like Listen or SetDeadline, Addr is
        // the address of the local endpoint being manipulated.
        // For operations involving a remote network connection, like
        // Dial, Read, or Write, Addr is the remote address of that
        // connection.
        Addr Addr

        // Err is the error that occurred during the operation.
        Err error
}

其中,net.OpError.Err 可能是以下几种类型:

*os.SyscallError 错误比较特殊,与具体操作系统调用有关:

type SyscallError struct {
        Syscall string
        Err     error
}

对于我们关心的网络错误,SyscallError.Err 一般为 sys.Errno 类型,与网络错误相关的常用值有:

  • syscall.ECONNREFUSED
  • syscall.ETIMEDOUT

看到这里,你可能忍不住要吐槽 Go 这种错误嵌套处理了,事实上,官方也意识到了这种错误处理的问题,在 Go 2中,可能会出现新的错误和异常处理方式,可以参见 GopherChina 2018 keynote 点评: RETHINKING ERRORS FOR GO 2.

当前阶段,我们依然要直面这种错误处理方式。为了方便大家理解 Go 网络标准库中处理错误的方式,我们把上面的错误嵌套整理了一张关系图:

明白了网络标准库中处理错误的逻辑,判断和识别各种类型的网络错误就非常简单了:对网络错误进行类型断言。以我们团队主要关心的 DNS 解析错误、TCP 无法建立连接、读写超时为例,判断逻辑可以是这样:

func isCaredNetError(err error) bool {
    netErr, ok := err.(net.Error)
    if !ok {
        return false
    }

    if netErr.Timeout() {
        log.Println("timeout")
        return true
    }

    opErr, ok := netErr.(*net.OpError)
    if !ok {
        return false
    }

    switch t := opErr.Err.(type) {
    case *net.DNSError:
        log.Printf("net.DNSError:%+v", t)
        return true
    case *os.SyscallError:
        log.Printf("os.SyscallError:%+v", t)
        if errno, ok := t.Err.(syscall.Errno); ok {
            switch errno {
            case syscall.ECONNREFUSED:
                log.Println("connect refused")
                return true
            case syscall.ETIMEDOUT:
                log.Println("timeout")
                return true
            }
        }
    }

    return false
}

这种错误判定方式除了能解决最开始提到的可靠性和准确性问题,也具有良好的普适性。即基于 net 的其他标准库,如 net/http 也支持这种错误判断方式。

扩展阅读

那些产品设计中的小惊喜

虽然技术出身,但是日常工作中经常需要单独的设计一些产品的原型,因此,需要对产品有一些感觉。

你也许被《人人都是产品经理》“荼毒”过,不用悲伤,我也是;你也许跟我一样技术出身,习惯了从技术角度看功能和需求,对产品和业务比较麻木,不要心急,我也是。你也许无数次的跟产品经理争论这个功能的合理性和必要性,谁也说服不了谁,最后你还是做了这个功能,仅仅是“厌恶”了跟产品无意义的浪费时间,没有关系,我也是。

然而,你要知道,大多数时候我们要做的产品设计其实都被前辈验证和解决过,是存在最佳实践的,也是可以从其他产品习得的。这种习得可能是一个特定的产品实现方式,但更多的是产品设计的思考和思路。这种习得,产品经理的整套方法论并不是必要条件,只需要你在使用产品的时候有意识的培养发现问题的敏感性和好奇心。

这里,我先把这几天使用产品过程中看到和体会到的一些产品设计中有意思的「小惊喜」整理一下,以后会不定期更新,争取能形成一个系列。

登录页面:简单的功能比你想象的更难更重要

随着 OAuth 以及开放平台的发展,国内绝大部分产品都支持微信、微博、QQ等多种社交账号的登录方式。这类登录方式不仅可以降低用户登录/注册的心里决策和输入成本,还能够从技术上简化整个账号系统的设计。因此,除了阿里和腾讯自成一体的公司产品,绝大多数产品都会在第一时间支持这类登录方式。然而,把这件事情做好的产品却寥寥无几。

以国内技术社区 segmentfault 为例,很多产品的社交账号登录页面可能是这样的:

从功能层面看,支持的登录方式挺多,把使用频率高的登录方式放在前面,这都没有问题。但是,我们的用户是健忘也是“无情”的。如果因为某种原因(APP更新、网络异常、更换手机等),用户需要重新登录,当用户再次看到这个页面的时候,是比较抓狂的:因为“登录”功能的使用频率很低,用户有很大概率不记得自己当初是以什么方式登录进来的;于是用户只能一个一个的去尝试不同社交账号登录方式来判断自己以前的账号是怎么登录的(这样做唯一能想到的好处就是注册用户数会提高,提前完成 KPI ……)。即使用户粘性高如微信,你去问问身边的朋友,他们的微信是手机号登录还是QQ登录,还是微信号登录,大部分人是搞不清楚的。

几天前用喜马拉雅听《晓说》,因为前一天晚上更新过APP,登录状态被踢了出来(这是个很糟的体验),尝试重新登录:

左下角提示微信为上次登录方式,这让我有了一点惊喜的感觉!

这是最佳的实践方式吗?私以为还可以再进一步:

之所以更加推荐这种将用户上次登录的头像和昵称显示出来的方式,不是因为这是东家咕咚APP采用的方式,而是因为,1)图片的阅读或者说注意力效率是远高于文字的;2)关系亲近的人之间可能会共用设备,显示头像和昵称有助于提高账号和登录方式判定的准确率。

在很多人眼里,登录页面这些改进太过于“鸡毛蒜皮”。但是如果你们的APP在登录页面的各个环节都有埋点的话,你也对这些数据足够熟悉的话,你会发现这些微不足道的改进对用户转化率的影响比你想象的重要得多。

说起转化率,使用社交账号登录一个非常常见也是非常糟糕的设计是:社交账号登录后,要求用户绑定手机/邮箱并设定密码。我能理解绑定手机号在我朝当前实名制政策下的无奈,但是大部分业务其实是不需要强制绑定手机号的。如果你是担心太过于依赖微信,要求用户必须有独立的登录用户民和密码,你应该想想你是不是太把自己当回事了?大部分情况下,我遇到这种社交账号登录后要求用户绑定手机和邮箱的服务,都会默默问候一个“产品13B”,然后直接关闭页面。

懂场景的音乐 APP

在听觉上,我应该算是典型的木耳。但是偏偏有喜欢在开车的时候听音乐。以前一直用豆瓣 FM, 歌曲推荐做的真心赞。但是豆瓣 FM 登录模块实在太渣,会因为各种原因把我从登录状态踢出来要求重新登录,老哥,我在开车呢,这样真的好吗?几经辗转,一次出行的时候,偶然打开了虾米音乐,因为连接了车载蓝牙,判断我是要开车听音乐,询问我是否进入车载模式:

进入车载模式以后,界面就变成跟老年手机一样,按钮巨大,按键也只有简单的几个上一曲,下一曲,暂停。心里默默念了一句: MMP, 这正是我想要的!

还没听完一首歌,又发现了一个小惊喜:

虾米把显示作者信息的地方用来显示歌曲的单词了!很Low是不是?是!很解决问题是不是?当然是!以后在车里想哼两句的时候终于不用因为不知道(不是不记得哟)歌词而随机脑补了。我知道很多技术背景的同学可能会对这种实现方式嗤之以鼻,但是以我对汽车软件行业平均水平的了解和自己折腾这台车歌词显示的经历看,这就是通用性最好、成本最低的方案!

坦率讲,虾米最主要的使用场景肯定不是车载,但是,透过这两个细节,我还是能够非常清晰的感受到虾米的产品在做音乐类 APP 是对场景的细致、全面的思考和实践。我们常常说一个功能或产品好用不好用,这其实是比较抽象和模糊的,如果你想找到具体的问题和原因,回到具体的场景和交互上,你一定能发现一些有趣的问题,同时给用户带来惊喜。

微信悬浮窗:悬浮的内容,流逝的时间

微信在不久前迎来了它的年度更新,其中最为大家津津乐道的一点就是悬浮窗设计:

但凡是常用微信进行阅读的同学,都能深刻的体会阅读文章与聊天消息之间切换的痛苦。悬浮窗一出,很多人大呼痛快。

使用一段时间后,发现了一个细节,随着悬浮时间的变长,悬浮的图标会逐渐变红,最终变成全红。有人说,悬浮窗是微信拯救不断被抖音等APP吞噬的APP使用时间所祭出的武器,此言不虚,但我想也一定有产品对提供更加良好的阅读体验的细致入微的思考和设计,毕竟,大家都是成年人了,「稍后阅读」往往就变成了「永远不读」。另一方面,我也比较悲观的认为,碎片化阅读其实是有非常明显的局限和天花板的,悬浮窗能做的已经接近这个极限。如果你真的是一个重度的微信公众号订阅者,悬浮窗是解决不了你的问题的,你应该关注一下订阅号展示形式的变化。

验证码繁与简

小区楼下有两个自动存取货柜,一般没人在家的时候,我都喜欢让快递老哥帮我把快递放在货柜中。回来的时候,凭验证码提取快递。一家是已经被中国邮政收编的速递易,它的验证码是这样的:

一家是顺丰家的丰巢,它的验证码是这样的:

都是6位验证码,不同的是前者使用的书“数字+字母”组合,后者只使用了数字。前者应该更加符合工程师思维:验证码空间要足够大,这样才能减少碰撞的概率,降低安全风险。但是,我倒觉得后者其实对验证码的认识更加深刻:

  1. 验证码是需要人工输入的,输入方式越简单成本越低。打一个不恰当的对比,前者需要用户会标准键盘输入,后者只需要用户知道如何打电话就够了。“会标准键盘输入”这个要求很过分吗?对你也许都不值一提,但是你要知道小区很多大爷大妈连手机上的T9输入法都不会用,只会手写输入,你要他会基本的标准键盘输入,显然是对你的用户群不够了解。
  2. 绝大部分用户短时间记忆一串数字是经过训练的,这种训练贯穿我们的小学数学教育以及功能机时代的“背电话号码训练”。因此,对于喜欢晚上出来遛弯取快递的同学来说,他们是可以看一眼验证码,然后放心的不带手机去取货。而背诵数字+字母组合,对很多人来说几乎就是不可完成的任务。
  3. 部分数字和字母字符不易区分,引入额外的错误率。典型字符如数字“1”和字母“I”, 数字“0”和字母“O”。
  4. 货柜的安全问题其实不在验证码。这个不方便细说,可以讲的是,如果你推演一下如何非正常手段获取货柜的的货物,你最后推演出来的方法一定不是傻乎乎的到带有好几个摄像头的货柜前去试验证码。

在验证码这个问题上,并不是说丰巢做的多好,而是想说速递易实在做得太烂:产品上的很多问题发展到今天已经是被很好的解决了,如果想在一些常用的产品设计上创新,请一定思考清楚这个「改变」是仅仅为了不同,还是因为当前设计存在很具体的问题。

浏览器地址栏存在多年的 bug

相信绝大部分人都注意到我们使用的浏览器地址栏是可以显示中文的:

但是,较真的工程师会告诉你:URL 地址的合法字符是 ASCII 字符的子集,是不支持中文的。也许浏览器地址栏显示中文是一个 bug?

虽然,平日里我是不太支持研发同学说 “这不是bug, 这是feature”,但是,这一次,我得说这的确是浏览器的一个 feature: 用户毕竟不是工程师,对于他们来讲,他们不关心也不在乎地址栏上面的字符是否经过 urlencode, 地址栏提供给他们的信息越直接越好。从这个逻辑出发,我们就不难理解地址栏中的安全绿色小锁以及直接显示中文了。当然,为了确保技术上的正确性,你通过 Chrome 地址栏复制的和开发者工具上看到的 URL 地址其实是经过 urlencode 过的: