开发者
2025-01-04
1 前言
马尔科姆·格拉德威尔的“一万小时定律”指出,持续投入一万小时的努力,足以使人在某个领域达到专家水平。按照每周 20 小时的练习量计算,每天大约需要投入 3 小时,十年左右才能达成这一目标。
从我写下第一行 C 代码算起,至今已超过十年。期间,我编写了超过三十万行代码,其中一部分在微信编写的代码,曾服务过超过一亿的用户。
尽管写了这么多代码,我仍不敢自诩为专家。但多年的“打工”生涯,日复一日地敲代码,也让我积累了不少感悟。“工多艺熟”,这些感悟既是对编程技术的思考,更是对职场人生的体味。毕竟,除了最初在学校学习的几年,我的编程生涯几乎都伴随着“打工”的酸甜苦辣(多是苦辣)。
2 持续学习
虽然大学是从 C 语言入门编程的,但是我在大学时主修的语言是 Java,毕竟 Java 是门非常成熟的工业语言,有非常丰富的框架,在国内的企业非常受欢迎,工作岗位也多。
我当时从 JavaServlets 入门 Web 开发,再学习了非常流行的 JavaEE 企业开发框架 SSH,即 Structs2+ Spring+ Hibernate,Struct2 负责控制逻辑关系,Spring 负责解耦,Hibernate 负责操作数据库。
而到我开始找工作时,SSH 的概念就变了,Struct2 被 SpringMVC 所取代,SSH 变成了 SpringMVC+Spring+Hibernate。
到我实习入职蚂蚁金服的时候,发现组里代码库操作数据库的 ORM 框架用的并不是 Hibernate,而是 Ibatis,后面又切换成了新的 MyBatis
而蚂蚁金服内部使用的也并不是 Spring/SpringMVC,而是自主研发出发的 Sofa 框架,Spring 社区后来觉得 Spring 框架过于重量级,不利于快速开发,又开发了更轻量级的 SpringBoot,而蚂蚁内部又推出了 Sofa 版本的 Sofaboot
去了微信支付后,前期都是在写 C++,使用微信内部自研的 svrkit 框架,到后期因为负责数据治理相关项目的缘故,开始使用 Spark + Python + HiveSQL
现在在 AWSS3,因为业务对性能和资源使用有非常高的要求,又开始使用 Rust,而历史业务又是使用 Java,兜兜转转之后,又回到 Java 的路子上。
细数下来,这些年来,我写过 Java、C++、Python、Rust、Javascript/Typescript 这些语言的生产代码。
除去工作之外,我还因为学习 SICP 学习了 Scheme,因为使用 Emacs 而学习了 EmacsLisp,想做独立开发赚钱学习了 Swift,想感受 RubyonRails 的魅力而学习的 Ruby,还有以前为了压测写的 Golang,还有各种语言对应的框架和库。
自我学习编程以来,学过的编程语言没有 10 种也有半打了。
我也从来不会把自己定义为某门语言的程序员,如 Java 程序员,C++ 程序员等等,我只叫自己做 Software Development Engineer。语言从来只是工具,只要你持续学习,遇到新的场景,自然就会学习新的编程语言了。
计算机的世界日新月异,可能几个月就会出个新框架,几年又会流行一门新语言,只有持续学习,才能持续保持自己的竞争力。
3 学好英语
领袖常说,「东升西降」,虽然不知道此种变化何时才能实现,但起码说明,目前是「西尚在上,东尚在下」,在计算机领域,尤其如此。
最前沿的技术都是英文资料,英语又是世界通行的语言,来自不同国家的开发者又会不约而同地使用英语来交流,因此学好英语既可以了解最新的技术潮流,又可以融入社区,建立自己的影响力。
疫情之后,越来越多的公司都开始推行远程办公,从全世界招聘开发者。这就意味着如果你英文过硬,甚至可以离开一线城市,避免高额的生活开销,在老家工作,陪伴在父母身边,同时赚取外汇;这对于饱受 996 困扰的程序员来说,未尝不是一条出路。
于我个人而言,坚持学习英语可能是我收获最大的投资之一。
熟悉我的朋友,尤其是我的高中同学可能知道,十年以前,我的英文可以说着实挺烂的:满分 150 分的英语,只考个及格的 90 分可谓是家常便饭,后来也只会笨学英语,到高三的时候能考个 120 分已经是巅峰水平。
但上大学之后,我也没有就此懈怠放下英语,大一还每天去晨读英语。
没有口语交流的条件,就自己创造,去网上找人聊天,当时还在一个叫 Interpals 10 聊天网站认识了全世界好多的人,其中还有一个是年龄相仿的土耳其女孩,我们还加了 Facebook,经常用 Skype 视频聊天。
大学毕业后就没有那么多的时间闲聊后就断了联系,最近看 Facebook 的动态,看她也穿上婚纱了。
工作后也一直阅读英文的技术文章,用英文搜索内容,在 Stackoverflow 和 GitHub 用英文回答问题,在 Discord 的英语学习频道找人聊天,把电脑和手机系统语言都换成英文的,从学习英语变成用英语。
后来在机缘巧合之下,从国内找到了加拿大 AWS 的工作,幸而有机会来加。
人们常说,路应该要越走越宽,而不是越走越窄;
而在我看来,英语就是夜里走路时手上拿着的手电筒,可以让我们走自己的路的同时,扫一下旁边那条道的情况,需要时及时转向,不至于一条路走到黑。
4 独立思考
微信以前一直有发最新 iPhone 手机的传统,但是那已经是 4 年前的美好时光了。
记得 2021 年是小龙明确年会不会发手机的第一年,他当时透露,那一年会发个铝片。
当时同事之间还在讨论,iPhone 也是一块铝片冲压而成的嘛,那发的是否还是 iPhone 呢,不发手机只是烟雾弹?
拆开年会礼物之后发现,的确是一块铝片,上面写着「2022 保持独立思考」。

小龙一直强调「独立思考」对微信的重要性,认为如果要选择一个最重要的品质,他会选择「独立思考」。
上级说的不一定是对的,老师说的不一定是对,学术机构说的也不一定是对,媒体说的也不一定是对,声音大的更不一定是对,毕竟有理不在言高。
比如微服务架构非常流行,许多公司都在搞微服务,那么单体架构是否就应该不使用?
作为初创公司或小团队,新业务是否要上微服务架构呢?还是先使用单体架构,业务发展起来再迁移到服务呢?
开发过程免不了要做各种决策,比如技术选型,针对你的需求,你可能会找到一打「看似」符合要求的组件,可能还会去网上找找对各个组件的评价,会发现众说纷纭,就需要自己独立对每个组件做出分析,找出其优劣,再结合自身团队的特点,做出决策。
关于独立思考,我最喜欢的是一句话是 HBO 出品短剧《切尔诺贝利》里面,科学家瓦列里·列加索夫希望克格勃释放调查真相同事乌拉娜·霍缪克的要求,说可以保证她是没问题的,克格勃头子回答的那句话:
Trust,butverify。(相信,但要核实)
5 先跑起来再说
这句话还有一个广为人知的变种:「又不是不能用」
很多的程序员都是完美主义者,尤其是读过《重构》和《设计模式》的程序员,会倾向于把很多时间来优化代码,做重构。
以前的我也会有类似的冲动,总会想时间去优化代码,但是项目肝多了之后,有种强烈的感觉,还是先把 MVP 上线,及早让用户体验。
如果没有用户使用,再好再漂亮的代码也没有任何意义了。
所以经常看到社区有人问做副业的时候,应该用什么语言和框架,PHP/Python/Ruby 会不会太慢,我的观点一直都是,先做个原型跑起来,先找到第一个用户再说。
当运行速度成为瓶颈时,你的业务已经非常大,肯定有足够的钱可以招一打的程序员把你的项目换成 Golang/Java 了。
对此,我很赞同坐我旁边大佬关于代码质量的说法:
make it run,make it fast,make it beautiful。
最近在做副业的尝试,有个深刻的体会,技术可能是商业里面最不重要的。
从零把产品做出来,推广给用户,用户只会关注你的产品是否好用,能否解决他们的问题。
他们既不会关注你是用 C++/Java 还是 Javascript 写的,也不会关注你代码写得是否优雅,与其执着于技术选型,不如先把产品干出来让用户试用。
6 顺手的才是最好的
经常会看到有人在社区提问,什么语言最好,什么框架最好,什么编辑器最好,什么操作系统最好。
「最好」是个相当主观的结论,也并没有针对所有场景的「最好」的解决方案,但是经常能看到社区有人因为哪个语言更好而吵起来。
或者有人在分享 A 的时候,有人会在下面回复 B/C/D 更好,然后又争吵起来。
让不禁让我想起《社会性动物》这本著名的社会心理学著作里面提到的团队认同现象,当球迷与某支球队产生强烈的认同感后,会将球队视为自我认同的一部分,这里他们会:
- 用「我们」而不是「他们」来称呼球队
- 将球队的成功视为个人的成功
- 对批评球队的言论产生防御性反应,将这些批评视为对自我的攻击
如果有人问我这个问题,我会回答「你顺手熟悉的工具的最好」。
即使是出于乐趣,编程的目的还是利用计算机解决问题,而解决问题最好的工具就是你最熟悉的工具。
除非你了解的工具不适用于你的问题,那么自然就需要一个新工具,也不要削足适履,矫枉过正。
当然,如果是为了满足求知欲而想去学习一个新的语言,那选择你感兴趣的就可以了。
当初在 2017 年学习 Rust,也只是因为大四没有课,时间充裕,想学点有趣的新东西,那时候 Rust1。0 才发布 2 年,可没指望能靠 Rust 找到工作
记不清在哪里看过的一段话:
我也曾问过自己类似的问题:
- 是不是好的东西就能流行?不一定
- 是不是我喜欢的东西就是好的东西?不一定
- 我会不会花时间精力在一个不一定会流行但是我喜欢的东西上?会
7 多与人交流
程序员固然是和机器打交道,但是本质解决的还是人的问题。
当初学习编程的时候,曾经有个误区,认为自己只要把技术搞好,就可以不去关心什么「人情世故」。
因此初入职场之后,我既是这么持有这样的想法,又是这样行动的,虽然不至于对其他人冷脸相对,但是难免会如好友形容那般:「孤傲」
但是被毒打时间久了才会发现,无论是在国内或国外,都难免会有「人情世故」,用英文来说,那叫 network and connection。
即使我技术能力过硬,也需要被人见到才行,和同事领导相处关系好,才可以在做出成绩的时候,「花花轿子被众人抬」。
所以我现在都是有事没事都和同事们聊天,既可以提升下熟悉度,也可以了解到许多部门八卦,还可以从同事们抱怨中找到潜在优化点,践行自己「Work hard and be nice to people」的理念。
这行做久了,会发现软件工程其实说到底,就是人的系统工程。
8 代码不是万能的
程序写多了之后就会有种幻觉,就是觉得什么事情都可以用代码来解决。
手里拿着锤子的时候,把什么都当成钉子来砸。
被毒打多才认清的事实就是,有很多事情是无法用代码来解决,代码只是个工具,只能在个合适的场景使用,避免路径依赖。
酒香也怕巷子深,只会写代码没啥用,还要写文章,在公司内部做分享,让别人能「看到你」。
编程肝项目的专业能力固然重要,但是也要有营销自己的软实力,就像一位长者说的那样:两手抓,两手都要硬。
不知道是中国人讲究谦虚内敛的品质,还是程序员「木讷呆板」的刻板印象,导致大家都不怎么营销自己。
有事没事和老板聊下天,增进下交流,经常露个脸,可能比肝十个项目还有用。
9 与优秀的人共事
从业多年,去过蚂蚁金服,微信支付和 AWS 搬砖,和各种各样的同事都共事过,有个越发强烈的感悟:
要与优秀的人共事
不仅能从他们身上学到非常到的优点,提升技术能力,可以学到最佳实践和工程经验,在 Code Review 的时候可以学到更好的编程方式,遇到问题时又有靠谱的队友帮忙和指导。
由优秀的程序员开发出来的系统的独特之处,知道什么叫简单好用的系统,形成自己的技术品味。
品味与美感这个词是很抽象,但是用过了好用的系统,自然就不会对那些粗制滥造,还靠老板背书强行推广的系统感兴趣。
而提高技术品味在提高我们的技术认知的前提下,又能反过来帮我们提高设计能力。
和优秀的同事共事的另外一个好处是可以建立高质量的人脉网络,利于职业发展,跳槽换赛道也多个选择。
虽然初始公司也有优秀的开发者,但是平均而言,大公司优秀程序员的比例会更高,毕竟他们更有竞争力的薪资福利,自然也有更高的招聘门槛。
比如微信就有所谓的面试委员会,除了招聘部门的面试官之外,还要通过面委面试官的考核,避免为了快速招人而降低标准。
所以个人建议应届毕业生,有机会还是去大公司,见识下。
虽然离职微信快两年了,我仍然想念当初同组共事的同事们,他们真的是技术过硬,人又超 nice,还乐于帮忙。
正如孔子所言:与善人居,如入兰芷之室,久而不闻其香,则与之化矣;与恶人居,如入鲍鱼之肆,久而不闻其臭,亦与之化矣
10 身体是一切的本钱
编程这么多年,落下一堆的职业病。
大学时候就有的鼠标手(腱鞘炎),工作几年之后「喜提」腰椎间盘突出,久坐下半身会麻痹,还有我曾经浓密黝黑的头发,现在也日渐凋零。
因为腾讯总部有免费的健身房,所以我基本工作日都会去健身房薅公司羊毛,2 天有氧慢跑,2 天无氧器械,坚持了快 3 年。也开始注意自己的饮食,尽量少油少糖不喝酒。
健身虽然不是包治百病,但是起码人显得有精神了,也有精力应付高强度的工作了。
只有失去才会懂得珍惜,也真的只有在开始吃药,去医院复诊,才会开始注意身体。
虽然编程很有趣,虽然养家很重要,但是还是要注意身体,毕竟身体是一切的本钱,垮就没有其他的精彩故事了。
11 总结
无论是编程,还是其他的技能,我感觉都是「马太效应」,你学得越多,你懂得越多,再学新的东西,你就会学得越快。
代码写多了才意识到,程序员的竞争力并不是写代码,也并不是哪门语言或者框架,其核心竞争力是通过技术解决问题的能力,又何必再去拘泥于哪门具体的编程语言或技术呢。
希望编程十年只是个起点,十年后可以再写一篇「编程二十年的感悟」
金句:
你顺手熟悉的工具的最好
只有失去才会懂得珍惜,也真的只有在开始吃药,去医院复诊,才会开始注意身体。
代码写多了才意识到,程序员的竞争力并不是写代码,也并不是哪门语言或者框架,其核心竞争力是通过技术解决问题的能力,又何必再去拘泥于哪门具体的编程语言或技术呢。
开发者
2024-05-05
发现一个信息推送小工具,ntfy。挺好用的,不需要我自己做的那个简陋版本了。

服务端是 Golang 开发的,然后官方支持的客户端:
- Phone
- Web APP (没有看到源代码仓库)
- CLI
无需注册,客户端订阅指定主题,然后直接往这个主题推送消息就行了。
这是官网上的几个推送示例:
curl -d "Backup successful 😀" ntfy.sh/wuhan
curl -H "Title: Dogs are better than cats" -d "Oh my ..." ntfy.sh/wuhan
curl -H "Tags: horse,unicorn" -d "Unicorns are just horses with unique horns" ntfy.sh/wuhan
订阅与推送的相关接口文档,官网写得足够好了,非常容易集成。
项目完全开源,包括 Android / iOS 端,可以自己部署,也可以就是用托管服务。
免费套餐对于个人使用也是足够了的,每天 250 条消息。

小问题:
- 只有 Web APP 支持 Markdown,而且支持的语法比较少,不大够。
命令行
ntfy pub -t <title> -f <filepath> <topic> <message>
-> % ntfy
NAME:
ntfy - Simple pub-sub notification service
USAGE:
ntfy [OPTION..]
COMMANDS:
help, h Shows a list of commands or help for one command
Client commands:
publish, pub, send, trigger Send message via a ntfy server
subscribe, sub Subscribe to one or more topics on a ntfy server
GLOBAL OPTIONS:
--debug, -d enable debug logging (default: false) [$NTFY_DEBUG]
--trace enable tracing (very verbose, be careful) (default: false) [$NTFY_TRACE]
--no-log-dates, --no_log_dates disable the date/time prefix (default: false) [$NTFY_NO_LOG_DATES]
--log-level value, --log_level value set log level (default: "INFO") [$NTFY_LOG_LEVEL]
--log-level-overrides value, --log_level_overrides value [ --log-level-overrides value, --log_level_overrides value ] set log level overrides [$NTFY_LOG_LEVEL_OVERRIDES]
--log-format value, --log_format value set log format (default: "text") [$NTFY_LOG_FORMAT]
--log-file value, --log_file value set log file, default is STDOUT [$NTFY_LOG_FILE]
--help, -h show help
Try 'ntfy COMMAND --help' or https://ntfy.sh/docs/ for more information.
To report a bug, open an issue on GitHub: https://github.com/binwiederhier/ntfy/issues.
If you want to chat, simply join the Discord server (https://discord.gg/cT7ECsZj9w), or
the Matrix room (https://matrix.to/#/#ntfy:matrix.org).
ntfy 2.11.0 (Homebre), runtime go1.22.3, built at 2024-05-13T20:11:29Z
Copyright (C) 2022 Philipp C. Heckel, licensed under Apache License 2.0 & GPLv2
-> % ntfy pub --help
NAME:
ntfy publish - Send message via a ntfy server
USAGE:
ntfy publish [OPTIONS..] TOPIC [MESSAGE...]
ntfy publish [OPTIONS..] --wait-cmd COMMAND...
NTFY_TOPIC=.. ntfy publish [OPTIONS..] [MESSAGE...]
CATEGORY:
Client commands
DESCRIPTION:
Publish a message to a ntfy server.
Examples:
ntfy publish mytopic This is my message # Send simple message
ntfy send myserver.com/mytopic "This is my message" # Send message to different default host
ntfy pub -p high backups "Backups failed" # Send high priority message
ntfy pub --tags=warning,skull backups "Backups failed" # Add tags/emojis to message
ntfy pub --delay=10s delayed_topic Laterzz # Delay message by 10s
ntfy pub --at=8:30am delayed_topic Laterzz # Send message at 8:30am
ntfy pub -e phil@example.com alerts 'App is down!' # Also send email to phil@example.com
ntfy pub --click="https://reddit.com" redd 'New msg' # Opens Reddit when notification is clicked
ntfy pub --icon="http://some.tld/icon.png" 'Icon!' # Send notification with custom icon
ntfy pub --attach="http://some.tld/file.zip" files # Send ZIP archive from URL as attachment
ntfy pub --file=flower.jpg flowers 'Nice!' # Send image.jpg as attachment
ntfy pub -u phil:mypass secret Psst # Publish with username/password
ntfy pub --wait-pid 1234 mytopic # Wait for process 1234 to exit before publishing
ntfy pub --wait-cmd mytopic rsync -av ./ /tmp/a # Run command and publish after it completes
NTFY_USER=phil:mypass ntfy pub secret Psst # Use env variables to set username/password
NTFY_TOPIC=mytopic ntfy pub "some message" # Use NTFY_TOPIC variable as topic
cat flower.jpg | ntfy pub --file=- flowers 'Nice!' # Same as above, send image.jpg as attachment
ntfy trigger mywebhook # Sending without message, useful for webhooks
Please also check out the docs on publishing messages. Especially for the --tags and --delay options,
it has incredibly useful information: https://ntfy.sh/docs/publish/.
The default config file for all client commands is /etc/ntfy/client.yml (if root user),
or "~/Library/Application Support/ntfy/client.yml" for all other users.
OPTIONS:
--debug, -d enable debug logging (default: false) [$NTFY_DEBUG]
--trace enable tracing (very verbose, be careful) (default: false) [$NTFY_TRACE]
--no-log-dates, --no_log_dates disable the date/time prefix (default: false) [$NTFY_NO_LOG_DATES]
--log-level value, --log_level value set log level (default: "INFO") [$NTFY_LOG_LEVEL]
--log-level-overrides value, --log_level_overrides value [ --log-level-overrides value, --log_level_overrides value ] set log level overrides [$NTFY_LOG_LEVEL_OVERRIDES]
--log-format value, --log_format value set log format (default: "text") [$NTFY_LOG_FORMAT]
--log-file value, --log_file value set log file, default is STDOUT [$NTFY_LOG_FILE]
--config value, -c value client config file [$NTFY_CONFIG]
--title value, -t value message title [$NTFY_TITLE]
--message value, -m value message body [$NTFY_MESSAGE]
--priority value, -p value priority of the message (1=min, 2=low, 3=default, 4=high, 5=max) [$NTFY_PRIORITY]
--tags value, --tag value, -T value comma separated list of tags and emojis [$NTFY_TAGS]
--delay value, --at value, --in value, -D value delay/schedule message [$NTFY_DELAY]
--click value, -U value URL to open when notification is clicked [$NTFY_CLICK]
--icon value, -i value URL to use as notification icon [$NTFY_ICON]
--actions value, -A value actions JSON array or simple definition [$NTFY_ACTIONS]
--attach value, -a value URL to send as an external attachment [$NTFY_ATTACH]
--markdown, --md Message is formatted as Markdown (default: false) [$NTFY_MARKDOWN]
--filename value, --name value, -n value filename for the attachment [$NTFY_FILENAME]
--file value, -f value file to upload as an attachment [$NTFY_FILE]
--email value, --mail value, -e value also send to e-mail address [$NTFY_EMAIL]
--user value, -u value username[:password] used to auth against the server [$NTFY_USER]
--token value, -k value access token used to auth against the server [$NTFY_TOKEN]
--wait-pid value, --wait_pid value, --pid value wait until PID exits before publishing (default: 0) [$NTFY_WAIT_PID]
--wait-cmd, --wait_cmd, --cmd, --done run command and wait until it finishes before publishing (default: false) [$NTFY_WAIT_CMD]
--no-cache, --no_cache, -C do not cache message server-side (default: false) [$NTFY_NO_CACHE]
--no-firebase, --no_firebase, -F do not forward message to Firebase (default: false) [$NTFY_NO_FIREBASE]
--quiet, -q do not print message (default: false) [$NTFY_QUIET]
--help, -h show help
参考资料与拓展阅读