我不太会玩竞技游戏,MOBA、FPS都挺菜的,只有战地系列略有心得,不至于被当薯条捞。 我有个游戏群,群友们很善良,愿意带我玩风暴英雄(一款MOBA游戏),我专职打野,他们团战4v5,因为我凑团战只会第一个暴毙。 打野会被对方抓,而我往往都跑不掉,当我问群友怎么办,群友说:
意识。你练练意识。
意识,查了下对应英文是 awareness 这个词,比如养成关注小地图的习惯,英文叫 Map Awareness。 意识也可以叫大局观,就是对(一场对局的)整体环境有意识地持续关注,接收游戏提供的多维度的信息,利用信息取得优势。 竞技游戏大多都有这种概念。
这篇文章罗列一些我在设计系统或程序流程、debug、或者单纯地计划怎么完成工作时思维的出发点、思路、引子,我不知道怎么给这些经验起个总结的文章名,“意识”这个词比较贴合我想表达的意图。
这些意识也可以用在工作外,只要是涉及信息处理的思考,我都会用,原理没变。 跑个题,说起来信息处理这个词,台湾的软件工程专业叫“资讯工程”,部分日本学校的计算机专业叫“情报工学”,我觉得还蛮抓住重点的,程序就是用来处理信息的嘛。
我拿不准这个文章的目标读者群体是谁,没准你读完就感叹“害,就这啊,浪费我时间”。 但是我观察到身边不少人的情况是,这个文章里说的道理懂不懂?都懂。 那碰到具体的事的时候咋呆住不知道下一步怎么做了呢。 肯定理论和实践之间还有一些没融汇贯通的点,我在这篇文章里根据我的经验串一串,希望能帮助到有这种情况的人。
#以程序的视角看设计
共情,同理心,empathy,是从他人的参照系统中理解或感受他人正在经历的事情的能力,也就是运用理解力与想像力等,尝试(在想像中)将自身置于他人处境或所在“位置”的能力。 – 维基百科:同理心
我能轻易地共情程序,共情到和同事商量接口时用“我”指代我负责的部分,“你”指代同事负责的部分。 如果我发现需要把程序和人分开说时也会以模块名代指程序,不过目前为止这种说法即使是第一次沟通,也很容易就被同事理解接受。 在本文中我会同时使用“程序”和“我”代表程序,让读者感受一下这种表达方式。
以程序的视角考虑问题,可以达到从全局视角转变为“置身事内”的效果,就像谷歌地图上放一个小人,地图放大展示街景。 比如用全局和共情两种方式叙述同一个数据查询过程:
- 全局: 用户使用浏览器进入页面,触发前端调用后端API,后端根据请求参数查询数据库并返回数据到前端,前端将数据以列表形式渲染到界面上。
- 将自己想象成后端服务:前端发请求调用我暴露的这个
foo
API,然后我根据请求中的条件,分页查询moo
数据库的bar
表,之后根据需求中要求的过滤掉不符合某某某条件的项,再把剩下的结果返回给前端。哦对了,如果失败的话,我还要打印日志并返回包含错误信息的响应。
两种方式叙述、思考的着重点不同,第二种方式更彰显后端在这个过程中的行为和动机,更易注意到全局视角下忽略的细节,魔鬼藏在细节中。
#流程通不通
先上两个相信大家都看腻了的材料:
|
|
这是一张极大概率会出现在教编程入门、计算机科学概论之类的课,比如斯坦福CS101、MIT6.00,的第一节课的PPT上的图。 这是对信息处理过程的抽象表示,输入-处理-输出,朴实无华,一看就懂。 不过我想在“输入”前再加一个“触发”阶段,原因见下文。
Any problem in computer science can be solved by another layer of indirection. (计算机科学领域的任何问题,都可以通过增加一个间接的中间层来解决。) – David Wheeler (computer scientist)
一句实在的真理。
大致流程通不通,是我设计程序流程的出发点,这是对可行性的判断,流程得先通了,再去想实现细节。 顺着程序的生命周期一步步想,带着动机想:
- 触发:在何时、由谁触发这个流程?时间条件:定时任务,到时间启动,父进程负责触发;对外暴露API,不确定的任意时机,外部调用;控件响应,用户使用之时,用户操作。
- 输入:这块是重点。为了完成任务,我需要最小范围知晓哪些信息?现有架构能否满足这种需要,把必要信息给到我这边?现有条件不满足,可以创造新的路径把我和信息源连起来吗?
- 处理:现在不想这步,可以想一点儿。
- 输出:谁,什么时候想要我处理好的结果?我怎么把结果给他,实时调用还是输出到某个中转里就不管了?
通了?好,我们接着一个个阶段铺开聊。
#触发,谁来执行我
我之前在这篇文章里写了一些相关内容,总结来看在这个阶段主要关注两点,时机和事件。
我们人类还是按着天然有序的时间生活的,计算机也配合人类按时间运行。 关注程序运行的时机,可以帮助我们设计执行顺序、定位逻辑错误。 比如有个系统,和交易所交换信息,那我们可以放心地断定,在非交易时间这个系统是不会收到来自交易所的请求的。 然后我们可以利用这个判断,比如因为非交易时间时,和交易所业务相关的数据库表内容不会再变化,我们决定备份数据库数据的定时任务的执行时间设置在每天交易所休市后,或者趁着非交易日上线升级。
再分享一个具体编程的经验,调用函数最好的时机是参数凑齐后的下一行,声明本地变量最好的时机是第一次使用该变量的上一行。
任何逻辑都需要一个触发它的事件。 做网络安全的朋友们对这个比较熟悉,执行漏洞的本质是找一个允许执行自定义输入的点。 破解付费软件也是,要改的就是输入许可证后点的那个确认按钮触发的认证逻辑。 很久之前的老电脑游戏,判断时间是直接读的硬件接口,不过现在的游戏都是调用操作系统提供的API,所以变速齿轮可以通过类似中间人攻击的方式(说mock程序员的各位更熟悉吧)修改这个API返回的值来欺骗游戏,进而达到修改游戏速度的目的。
那你可能问了,有没有“正当”点的用法,有,比如反过来利用,通过控制事件来抑制逻辑执行。网站和手机应用的弹窗很烦人,触发弹窗的事件是什么?可能是“广告内容加载成功”,那我们配防火墙把获取内容的网址给屏蔽了,或者单纯屏蔽还要让应用等到超时,直接代替返回404响应更快。应用取内容得到非200响应,它少说也得走到异常处理吧,幸运的话抛异常直到被全局兜底捕获,“弹窗”这个逻辑就被跳过了。再比如手机的摇一摇广告,在外部mock这个应用对陀螺仪的读取(如果能的话),总是返回相同值,弹窗条件判断永远是false。
进程树,从PID1开始,各进程由启动关系编织成一颗树。 每次我看到树形结构,我就想象用手提着根节点,像摇铃一样抖一抖,整棵树在半空中晃动。 不止有进程树,逻辑间的相互调用的顺序也会形成各种形状,简单的流程可能是根线,复杂的流程可能会出现网状结构,那是有一调用多或被多个调用。 我们仍然以B/S架构打比方,用户在浏览器上打开多个页签,此时用户行为是根节点,你可以想象一下一个人攥着一把绳子的样子,每根绳子的另一端绑着一个服务器机柜。
#输入,从外界获取信息
了解如何使用各种手段获取信息是这篇文章的重点,在这个章节中我会混合以“程序”和“人”的角度讨论信息获取。
逻辑本身是无状态的,数据总是要存在哪里才不会在逻辑结束后消失(或者说,数据还没被抹掉,只是丢失了获取数据的地址/指针/标签/绳子)。 想象下,你是一个程序,刚刚被操作系统启动起来,懵懂地睁开眼看向周围。 就像一个失忆但智力正常的人,在一个陌生的设施里醒来。 幸运的是,你手上有一本日记和笔,日记开头几页不是空白的,指导着下一步要去哪里,一些门的密码是多少(配置文件/启动项)。 你按着日记第一页的“入口函数”章节开始探索设施,使用日记里的密码打开最初的一些门,门后有打开其他门的提示。 随着活动范围逐渐增大,你对各个房间越来越熟悉,行动越来越得心应手。
想想婴儿吧,婴儿除了固化在大脑里的动物行为外,可以说是没有一点智能。 你我能读文字,还会敲键盘,这些都是通过和现实世界互动后学出来的。 而且人类和程序相比还有个很大的优势:主观能动性,我们会主动对感兴趣的事做出行动,不需要事事等待外部刺激让我们被动反应。 有些事是要等待的,不过咱们别干等,看看利用手头的东西能捣鼓点啥,别闲着。
#想要的信息在哪
好,我现在提起干劲准备继续完成工作了,别人已经给了我一些信息,但我不知道接下来该怎么做。 嗯……谁可能知道怎么做? 同组的同事?对接部门的同事?遗留代码的原作者?热心网友?
会不会我需要的信息在现成的文档里,那么这个文档可能在哪里?同事的电脑里?试着在公司内部平台上搜搜看? 我可以在网上搜搜,没准有现成的方案。
我刚刚调到这个组,我想知道foo环境的数据库密码,但是我很内向不敢问同事。 配置文件里必然会配置这个信息,我已经有项目的git权限了,这样可以查到密码。
这个平台也太难用了,在页面上根本找不到想找的功能的入口在哪。 开F12看看侧边栏菜单、首页请求的HTML源码和json响应,用汉字搜索一下碰碰运气,顺藤摸瓜找到功能藏在哪个子子子菜单栏下。
信息独特的特性使得它“所见即所得”,“vidi, vici”,有看到信息的能力就等于有复制所看内容的能力。 相同的信息可以同时存在多份,不一定事事都要问人,想想它还可能存在于你手头资源的哪里? 多主动思考行动,别干等着被动接收,到了那时候可能想主动也难了。
#怎么拿到信息
知道信息在哪不等于可以获取信息。 记者知道一些会造成丑闻的文件存在政府电脑里,但没办法看到。 不擅长学习的学生知道下次考试的试卷已经打印出来锁在老师的抽屉里,但没办法拿到。 打开一个别人分享的新闻链接,却被登录墙或付费墙挡住看不了。 因为没有调整目录权限,手动启动的服务可以正常读取文件,但被daemon自动拉起的服务却报IO异常。 这些都是权限问题,不过想打开宝箱不止找到钥匙一种解法,还可以找到一个会撬锁的人。
可以利用现有信息获取更多的信息,直到拥有足够多解决问题的信息。 比如因为给程序配置了数据库连接配置,所以程序有权限查数据库;因为前端通过请求体提供了查询条件,后端知道根据什么条件查询数据库。 又比如用一个关键字查到几篇文章,在文章中找到更多可以当关键字的词语。 又比如介绍对象,熟人之间相互聊,很快就能突破熟人小圈子,把老家出身但在孩子所在城市工作的适龄青年的信息全收集起来……
知道某些人了解答案的话,《提问的艺术》里的技巧掌握住,一两轮问答找到答案,答者轻松你也轻松。 大胆开口问,不要怕别人多优秀你多比不上,一般优秀的人也是亲切的人,再忙也会简单回答,适度适量没事的。 如果怕一对一问,就在相关的论坛、群组里问,提问压力会小很多。 问了,不一定收到回答,不问,永远收不到回答,随缘问。
还有一种情况,是知道在哪,能拿,但是和信息源之间隔着山不好拿,这个就需要巧妙设计David Wheeler提到的中间层了。 比如本系统想做一个功能,需要关联系统提供相关信息,但是关联系统目前没这API,呵,排期可排吧,正常走流程半年也上不了线。 那怎么办呢,请示领导让领导帮忙和对方领导沟通,看能不能插个队,搭一个人际关系的中间层。 比如想要在监控平台上监控一个外部服务的状态,但是这服务部署在了山的那边海的那边,俩服务器直连不通。 需要找网络管理部门一起商量,看什么方案既能把状态信息飘洋过海传过来,又不违反公司这个那个的安全策略。
#搜索,搜索,搜索
搜索的目的是节约寻找答案的时间,因为东翻西找寻找答案的过程本身没啥意思。 计算机世界中的信息以二进制或文本形式表示,人类看不懂二进制,所以大部分情况下只能搜索文本形式的信息。 好消息是,文本形式的信息真的很多,只是有些没展示在明面上所以没有感知到(比如上面通过json响应找菜单位置)。 这也呼应了多思考信息在哪里的重要性。
我相信把文章看到这里的你知道如何通过搜索引擎获取信息,这章节主要是提醒你可以在更多地方使用搜索技巧。 不少工具都带有搜索功能,比如IDE项目内搜索、浏览器页面内搜索、操作系统的文件搜索…… 不过即使看文本的工具没有现成的搜索功能,只要能想办法把文本弄到编辑器或文本文件里,就能搜索了。
再提到破解软件,如何定位输入许可证后按下的确认按钮在反编译后的代码中的位置呢? 输入框上面应该有文字提示吧,比如“请输入许可证”之类,就搜索这句话就行了,按钮控件应该离这个文本控件不远。 可能反编译工具不能输入汉字,那先把这句话翻译成UTF-8、国标格式的字符串,再搜索这个字符串的二进制格式的位置。 因为我们知道这些文字在程序中是以字符的格式放在一起的,很少有人给文字做混淆。
能够总结出想搜索内容的关键字也是搜索能力的体现,关键字找得越准,搜索结果越准确。 要找这个内容中最独特,在其他内容中最不可能出现的词语。 现在有AI加持的搜索引擎比如perplexity了,利用AI可以把自然语言描述的问题转换为关键字。 虽然AI的回答不一定正确,但在我不熟悉的领域,AI回答中一定含有我不知道的领域术语,可以拿这些术语作为新一轮搜索的关键字。
善用高级搜索功能。 搜索引擎比如谷歌是有一点简单的搜索语法的,比如双引号代表精确搜索。 搜索引擎之外的工具的搜索功能也可能有语法,比如数据库有SQL、Prometheus有PromQL、Kibana有KQL、文本编辑器有正则表达式。
这里给一个不断搜索找到答案的真实例子。 有一次测试环境服务器重启了,我负责手动启动我们系统的服务,但我发现我们服务需要依赖其他系统部署在我们服务器上的代理服务,组里没人知道怎么启动这个代理服务。 还好我们的代码里有这个代理程序的名字,我用find找到了代理二进制文件所在的目录,有好几个程序。 该敲什么命令启动呢?时间隔太长了,history里也没有包含程序文件名的启动指令。 我心想,很多系统都和这个系统有关联,想必有很多服务器都部署了这个代理服务。 正好隔壁组刚刚部署了一个新环境,我找人借来服务器账号密码,从那个服务器的history里找到了启动指令,又通过ps复核了指令。 但是程序有好几个啊,history显示部署的人也不是很熟练,几个服务来回启停,搞不明白顺序。 还有什么地方可能有顺序信息?在公司的Confluence内容分享平台上试试运气吧。 用这个系统的名字做关键字,有好多无关的匹配,找不到。 于是我想,如果有页面写了启动顺序指令,那必然包含程序文件名,于是换用精确搜索(双引号)程序文件名,成功找到了好心人留下的页面,重启了代理服务。
多主动搜索,不要被动等待推送。
#处理,系统边界内的事
好,现在需要的信息都到手了,该考虑怎么处理了。
此章节省略若干字,“常规”的编程方面的考虑都包含在这一阶段: 认知负载、封装、设计原则、设计模式、算法时间空间….. 已经有足够多优秀材料来讲解这部分,比如可以看这篇文章中的书单和评论区中的书名,都是我看过觉得不错的。
人处理信息的话,我觉得值得分享的是:书《学习之道》、《思考,快与慢》中都提到了大脑有快(发散思维)和慢(专注)两个系统。慢系统需要回忆和逻辑推理,对具体问题反应慢但准确。虽然快系统下意识反应,对具体问题迅速但不准确,但在大脑放松的发散思维状态下,本来在专注状态下分门别类的各领域知识会互相关联,适合思考非常规解答、创意、脑筋急转弯等事务。所以学习知识要劳逸结合、杂食广读。
另外人和程序都可以找代理分担工作,程序可以借助API,人可以借助身边人。 不要什么事都自己抗,忙不过来就果断寻求帮助,人也是,机器也是。
#输出,谁需要给谁
信息一定要有实际的下一手的消费者,否则花费资源收集处理保存便没了意义。 数据库、文件系统等就像仓库、快递驿站和快递柜,信息放在那里,或永久保留或暂时中转,生产/传递信息者可以扭头去忙别的。
#不可能收回发布出去的信息
你看,就像风吹散了羽毛就不可能再捡起来一样,流言蜚语和诽谤一旦从我们嘴里说出来,就不可能再收集起来。 – 预言故事《捡起鸡毛》
不能频繁破坏性变更API,原因是你不知道API的消费方利用了你API响应中的哪些信息。 我搜不到了,记得Python还是哪个语言,有个版本改变了标准库一个集合中排列元素顺序的算法(显然这个集合的功能本身是不保证顺序的),但是有公司提了issue,说他们利用了该集合的内部元素顺序,现在逻辑一团乱。 最近我参加了一个在线会议,组织会议的是我司的一个基础系统,它想改造内部保存数据的几张表结构,会对相关API返回数据的序列号的含义产生影响,拉个会通知各系统配合改造,我一看参会人数好几十,心想基础系统想做变更真是不容易……
信息尤其个人信息,公开状态就是0和1,一旦上传到网上过,默认算作(被出卖或攻击)公开,没有后悔药。要么永远捏得死死的不上传;要么坦然面对,亡羊补牢,预防之后可能出现的身份盗用、骚扰等情况。也可以准备很多套可弃用的身份,比如在中国手机号相当于网络身份证,可购买三大运营商的虚拟手机号。
#好东西留一份
你的表情包很好,下一秒就是我的了(复制信息)。 之后能利用这次获得的输入或处理结果做得更快更好吗,能的话把信息存一份给自己用(缓存)。 信息复制保存成本不高,经过手的信息觉得之后还有价值、私自保存不违规,就留一份。 有同事喊我百宝箱,因为我习惯(在加密盘里)保留重要工作文件,把经手的工作流程要点记下来(然后坦然忘掉),需要的话搜搜大概率能找到。
前段时间看个文章说,我们这一代是“下载一代”,上一代不频繁使用互联网,下一代云原生,就我们这代人有把东西下载到本地保存的习惯。 我觉得这说法没什么道理,习惯是能后天养成的,没这个习惯说明平时没这个需求。 但是我有点担心下一代不会操作电脑,没有主动搜索思维,毫不在意地提供个人信息给平台,即,不了解信息的价值、不了解真正持有信息(保存到本地)的重要性。
广告环节!
minecraft-access 是一个帮助视障者玩 Minecraft 的 mod 。目前它的活跃开发者只有我和另一个人,而新的需求和bug又是那么多。如果你想通过开发 mod 来了解 Minecraft 运行逻辑的话,这mod绝对是你最棒的选择,因为要大量读并改游戏源码。如果你感兴趣,这篇文章多介绍了些项目情况,期望我们在 PR 页面和 issue 页面再见。