[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"list-\u002Farticles\u002Ftech\u002F2":3},{"items":4,"total":124},[5,23,31,39,49,58,67,78,87,95,105,115],{"id":6,"type":7,"slug":8,"title":9,"date":10,"category":11,"tags":12,"body_markdown":17,"permalink":18,"excerpt_src":18,"media_type":18,"media_title":18,"media_author":18,"media_url":18,"rating":18,"layout":18,"pv":19,"admin_only":19,"created_at":20,"updated_at":20,"deleted_at":18,"status":21,"cover":22},133,"article","idea-of-enterprise-it-system-construction","企业信息化及系统建设的思路","2023-04-18 12:00","tech",[13,14,15,16],"企业管理","信息化","系统建设","工具","\n\n> 之前写过一篇《[谈谈我对工具软件的理解](\u002Farticle\u002Ftech\u002F2023\u002Fmy-comprehension-on-tools.html)》，与本篇有较大的关联性，如果不清楚写了什么，建议先复习一遍再看本文。\n\n我在招聘IT产品经理的时候经常问2个问题：\n\n- 所有人都很讨厌报销流程，那为什么每家公司都还要设置报销流程？\n- 一家公司在什么时候设置报销系统是合适的？\n\n第2个问题先按下不表，先说第一个问题。\n\n\u003C!-- more -->\n\n首先可以做一个思维实验：假设报销流程是不对的，那么一两家公司做错的话是可以理解的，但所有的公司都做错是不太可能的，因此可以反推出报销流程的设置是必要的，这应该是一个可靠的结论。\n\n那么，为什么一个人人都很讨厌流程却是必要的？其实这个问题倒也不难回答，不妨反过来想一想，如果公司不设置报销流程的话，会怎么样？\n\n报销的本质是“先斩后奏”，即员工先用自己的钱把某件事情做了，然后再找公司要回来自己支付的钱。本质上它是在没有（充分的）事前审批的情况下花企业的钱。\n\n例如打车费用报销，并不能事前申请需要花多少钱，它可能是50块，也可能是55块或者59.5块，只有花了之后才能知道它是多少。公司只能通过事后报销的方式才能准确地知道这一笔钱的花销是多少。而正因为是事后报销，如果不加控制的话，有可能今天打车报销是50块，明天就变为了150块。因此，在报销的流程中，进行真实性和合理性的核验是一个必要的过程，不然一定会发生舞弊。\n\n## 流程与系统\n\n所有的公司都有报销流程，但并不是所有的公司都有报销系统。有很多公司会使用其它方式来走报销流程，例如邮件审批。\n\n在没有系统支持的情况下，流程完全是可以执行并且起到管理效果的，只要每一个步骤和控制点都得到有效的执行。这里的“控制点”尤其重要，例如一定要有财务人员核实发票，一定要有直属上级核验真实性。\n\n而系统可以帮助公司更好地支撑流程的落实。以邮件审批为例，在执行的时候很容易发生这样一些问题：\n\n- 需要多位上级审批，是发给多个收件人，还是每个上级回复邮件后再转发给下一位？\n- 在项目制的组织中，应该发给专业上级审批还是项目负责人审批？\n- 如果多位上级同时收到邮件，高级别的审批人先审批了，低级别的审批人还需要审批吗？\n- 如果提交的报销材料有缺失或错漏，但是老板已经同意了，还需要让申请人重新发起申请吗？\n\n如果将这个审批使用系统来实现，则可以完全规避以上问题，也就是说，系统在落地流程时可以起到的作用：\n\n- 规范填写，避免审批材料的错漏\n- 规范审批流，避免审批过程的缺失、错漏或者顺序错误\n\n除了解决邮件等线下流程的问题之外，系统本身还有一些独特的优势：\n\n- 为审批者展示关键信息，有重点地进行审批\n- 为员工提供更好的申请和查看体验\n- 对流程进行归档，使数据留痕且不容易被篡改，方便审计等场景\n- 使流程中的数据变为结构化的数据，便于进行统计、生成报表等\n- 使用新技术为企业带来新的管理方法，例如合同电子签等\n\n这里面的点都可以展开说，后续有机会再单独写。\n\n## 企业管理的方法\n\n报销只是企业管理中的冰山一角，本质上，所有的流程背后都企业管理的方法。这些方法有一些来自企业自身的摸索和迭代，有一些来自成熟的方法论。例如人力资源管理、财务管理、税务管理、采购管理、市场管理、研发项目管理等等都有非常成熟的方法论。\n\n每一种方法最终都会落地为企业的管理策略，不同的企业可能有不同的选择，例如：\n\n- 奈飞工作手册：招最好的员工，用最少的流程，让员工自驱\n- 字节：招最多的好员工，用OKR对齐目标，用文化统一行为方式，让员工自行产生力量\n- 华为：用高薪酬激励员工的积极性，用流程管理确保组织以正确的方式做事\n\n同一企业在不同的发展阶段也可能有完全不同的管理策略，此处不做展开。\n\n但需要明确的是，无论企业规模大小，无论选择哪种管理策略，企业的管理方法是始终存在的：\n\n- 你需要上班打卡，这是考勤管理方法的体现\n- 你请假不用提单，这也是考勤管理方法的体现\n- 你需要写日报，这是项目管理方法的体现\n- 你需要开晨会，这也是项目管理方法的体现\n- ……\n\n## 企业管理系统建设\n\n在上一篇《我对工具的理解》中，我讲到了工具是可以落地方法的，这一点在企业系统中更为重要。所有的企业管理系统最终都是为了落地企业管理方法。\n\n在系统建设过程中，都需要先了解清楚企业是如何思考和如何选择管理方法的。仍然以报销为例，下面这些问题不回答清楚，是没法做系统的：\n\n- 报销是侧重真实性核查还是合理性核查？\n\t- 真实性：各种消费清单、发票上传\n\t- 合理性：对比行业价格做合理性判断\n- 需要多高的管理者进行审批？是直属上级还是逐级审批直到CEO？\n- 企业能承受多大的风险？（1分钱都不可以损失？可以在一定范围内允许少量损失？）\n\t- 高级别领导是否有可以免审的限额？\n\t- 是否容忍少量发票缺失问题？\n- ……\n\n在企业采购系统的时候，往往会有一个长达数周到数月的调研阶段，实际上就是在确定类似的问题。只有在完全确定之后才能拿出系统实施的详细方案出来。\n\n于是到这里我们可以尝试回答开篇的第二个问题了：一家公司在什么时候设置报销系统是合适的？答案中至少有一部分就是：在公司的管理方案确定的时候。（当然还有其它的因素，这里不展开。）\n\n## 企业咨询\n\n上面似乎已经解答了所有的疑问，但实际上在真实的公司中，事情往往没有这么简单：公司是从零开始一步一步发展起来的，而不是设计出来的。没有任何一家公司是在一开始就设计好了所有的管理方法，然后再用数年或数十年的时间把这些管理方法落地下来。更现实的情况往往是公司在一开始什么方法都没有，当问题不断出现的时候才不断去学习管理方法。\n\n在企业信息系统建设的时候，往往面临的情况是：公司有这个问题，但没有想好要怎么管理，或者不知道怎么管理，希望通过建设一个系统来解决问题。\n\n通过前面的分析就能了解，从问题到系统，中间缺少了一个非常关键的事，就是方法。所以企业信息系统建设往往是一边找方法，一边做系统。\n\n如果公司对这些问题的解决思路完全没有想法的话，一条很便捷的路是：先买一个成熟的系统用着，一边用一边学。上一篇《我对工具的理解》一文中我说过，工具是带有方法的实践的，在企业中也是一样。当买入了一个成熟的系统，在使用的过程中，自然而然地会接受系统带来的一些管理方法。这条路径对小公司来说尤其适用。\n\n而如果公司自己本身有一些诉求或者倾向，此时更合适的路径是引入企业咨询。\n\n所谓企业咨询，即是由专门的咨询公司的人员就公司现状和诉求进行梳理，结合他们丰富的公司管理学的方法和经验，梳理出一套适合公司实际情况的管理方法。\n\n在购买或者建设比较大型的企业信息系统的时候，引入咨询公司也是非常常见的，此时就由咨询公司负责现状和诉求的梳理，输出具体的方案，然后再由系统供应商或者公司自己的研发团队将这套方法实现到系统上。\n\n事实上，即使不专门购买咨询服务，只要购买比较大型的企业信息系统，系统的供应商也或多或少会提供一些咨询服务，只是没有专业的咨询公司能力强。\n\n## 小结\n\n- 流程和系统都是落地企业管理方法的手段\n- 系统可以比线下流程更标准规范，并带来更多优势\n- 所有的企业都有自己的管理方法\n- 信息化系统建设路径：问题-管理方法-系统\n- 管理方法不清楚的时候可借助成熟软件来学习\n- 企业咨询也可以帮助企业找到适合的管理方法\n\n## 题外话：关于效率\n\n在大部分企业管理中，提升员工具体的操作效率一般来说是不做重点考虑的。主要的原因可能是员工具体操作的效率并不会从根本上影响公司的运转，效率低一些不会带来致命的风险，高一些也不会带来质的提升。（可能对制造业或者实业来说还是重要的，这里不展开。）\n\n这一块如果展开讲的话也可以上升为企业协作工具，后续有机会单独开篇讲。\n",null,0,"2026-08-28 04:37:17","published","",{"id":24,"type":7,"slug":25,"title":26,"date":27,"category":11,"tags":28,"body_markdown":29,"permalink":18,"excerpt_src":18,"media_type":18,"media_title":18,"media_author":18,"media_url":18,"rating":18,"layout":18,"pv":19,"admin_only":19,"created_at":20,"updated_at":20,"deleted_at":18,"status":21,"cover":30},134,"my-comprehension-on-tools","谈谈我对工具软件的理解","2023-02-07 09:00",[16],"\n## 零\n\n我曾经学习过一段时间的乐器，乐器的学习提升过程中，会有瓶颈期，也就是在很长一段时间内感觉不到自己的进步。\n\n有一次，我在练琴的时候心情非常差，陷入了“学不好了”的情绪中，甚至一度开始怀疑，是否是因为我手上的乐器太过便宜，所以没有办法把乐曲演奏得很好听？\n\n但是我还没想完，我的老师们就来了，顺手拿起了我的乐器就演奏了一大段。那一瞬间我感觉他们手上的乐器并不属于我，虽然是同一个东西，但出来的声音却完全不一样。\n\n从此以后我就再不纠结了，我知道当声音不对的时候，原因只有一个，就是我没有学会方法，或者有可能我的脑子知道应该怎样，但是演奏时做不到。\n\n这段经历一直影响着我对工具的理解：没有差的工具，只有不会用的人。\n\n\u003C!-- more -->\n\n## 壹\n\n时间管理是一个指导人如何管理自己的时间和事情的领域，在这个领域中有一个很有名的方法，叫GTD——Getting Things Done。甚至有一本同名的书，专门阐述这一方法。\n\n简化版的GTD的核心做法是这样：\n\n1. 随时记录所有的事项到收集箱\n2. 每天对收集箱进行逐条整理，需要今天做的任务放到“今日”列表\n3.  按今日列表中的任务开始一天的工作\n4. 今日没做完的任务放到明天一并整理，再次安排\n\n它背后有几条指导思想：\n\n1. 清空大脑，所有你想到的新任务都应该记录到收集箱，而不是放在大脑中\n2. 只按今日列表做事，保证工作是聚焦的，这是提升效率的关键\n3. 每天对收集箱中的任务和前一日未完成的任务逐条整理，这能确保所有的任务不会被遗漏\n\n如果真的掌握了这几条指导思想和具体的实操方法后，Todo工具反而就显得不那么重要了。无论是哪一款Todo软件，清单\u002F列表、添加和移动任务、完成任务都是基本功能，只要按照GTD的方法来使用它，就是一款不错的Todo软件。\n\n即便没有Todo软件，只要能区分出列表（收集箱和今日列表）、条目（任务），也可以充当任务管理工具：能添加任务到清单、能在清单间移动任务，能操作完成任务即可。\n\n我曾经有很长一段时间在用代码编辑器管理自己的任务，效果很好。甚至不用软件，而是用便利贴这样的实体工具，遵循同样的思路，也可以收到很好的效果。除此之外，小团队协作的时候用的看板，其实也是差不多的思路。\n\n![使用代码编辑器进行任务管理](\u002Fassets\u002Ftech\u002F2023\u002Fmy-comprehension-on-tools\u002F01.png)\n\n这就叫，方法大于工具。\n\n> 但工具可以提高具体操作的效率，就像好的乐器的确可以让演奏更轻松。这一点不可否认，但这也是大家认知最丰富的一点，不需要更多解释。\n\n## 贰\n\n如果一个领域不是完全全新的，那么必然会有很多同类工具。比如在当下，记账工具、任务管理工具、笔记工具都呈现着遍地开花的状态。\n\n以笔记软件为例，有经典的印象笔记，胜在采集和资料搜索功能强大；有Obsidian，强调数据本地存储和Markdown语法；有flomo，强调卡片笔记法……\n\n这些软件都有各自的铁杆用户群，虽然互相不能说服，但乐在其中的状态却是一样的。\n\n而习惯了不同软件的人在应对笔记的态度上也会不一样。印象笔记的用户可能会习惯将看到的资料收集下来，然后在有空的时候再去整理，在需要的时候去搜索查找之前的资料。Obsidian的用户则可能会更习惯自己记录，而且会将格式整理得干净清晰，数据也都留在本机方便自己管理和备份。而flomo的用户则更可能习惯捕捉自己脑海中一闪而过的小想法，将它们都记录下来，然后回头再去回顾，或者整理成为长文。\n\n有一句话给产品经理的话是这么说：当你提供一个功能的时候，实际上就是在鼓励用户使用它。这说明，工具是在传达一些理念和方法给用户的。像flomo这样的工具，无时无刻不在宣导自己的“卡片笔记卡”，在这个方法背后也有对应的书籍和理论，只不过用户在使用这个工具的时候，通过工具的界面交互，自然而然就完成了这个方法的应用，并不需要用户先去读书了解卡片笔记法。\n\n同样，回到GTD的例子，每天对任务进行整理并得到今日任务清单是最重要的一步。Any.do、滴答清单等软件都有类似的功能，它们会在每天早上自动弹窗，将收集箱中的任务一条一条列出来，协助使用者整理出今日任务清单。如果没有这个功能的话，用户对GTD方法的实践将会大打折扣。\n\n![滴答清单每日提醒](\u002Fassets\u002Ftech\u002F2023\u002Fmy-comprehension-on-tools\u002F02.png)\n\n因此，尽管方法大于工具，但好的工具软件能将方法体现出来，长此以往，会帮助更多人掌握这些方法。\n\n## 结\n\n方法大于工具，掌握方法是比挑选工具更重要的事情。如果你没有办法主动去学习各种方法，那么挑选工具仍然是有意义的，因为它会潜移默化地教会人一些方法。\n","https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Ftech\u002F2023\u002Fmy-comprehension-on-tools\u002F01.png",{"id":32,"type":7,"slug":33,"title":34,"date":35,"category":11,"tags":36,"body_markdown":38,"permalink":18,"excerpt_src":18,"media_type":18,"media_title":18,"media_author":18,"media_url":18,"rating":18,"layout":18,"pv":19,"admin_only":19,"created_at":20,"updated_at":20,"deleted_at":18,"status":21,"cover":22},129,"all-about-ic-card","关于非接触IC卡的一些记录","2022-10-08 17:37",[37],"IC卡","\n今年上半年因为捣鼓门禁的原因，重新研究了一下非接触IC卡，记录一下。\n\n## 概念和技术特征\n\n一般使用的非接触IC卡全称叫 Mifare S50 1K卡，是否严谨并不重要，总之用这些名字能找到一些确切的资料。\n\n工作频率：13.56MHz，也被称为高频卡。感应距离大概在1cm左右。\n\n## 存储结构\n\nMifare S50 1K卡的存储空间总共有1K。分为16个扇区，分别编号为0-15扇区。\n\n### 扇区内结构\n\n每个扇区的结构如下：\n\n```\n块0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n块1: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n块2: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n块3: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00\n```\n\n> `00`表示一个字节，使用16进制表示。\n\n可以看到扇区分为4个块，每块有16个字节。\n\n其中块0-2是数据块，块3是控制块。即块3不能用于存储数据。\n\n\u003C!-- more -->\n\n### 扇区控制块\n\n块3为扇区控制块，用于控制扇区的访问权限。块3的结构如下：\n\n```\n密码A：00 00 00 00 00 00\n控制位：00 00 00 00\n密码B：00 00 00 00 00 00\n```\n\n密码A和密码B的默认值为`FF FF FF FF FF FF`。\n\n控制位默认值为`FF078069`，实际有效的只有前3位，第4位可以忽略。它的具体含义不展开，可参考\u003Chttps:\u002F\u002Fblog.csdn.net\u002Fweixin_40600325\u002Farticle\u002Fdetails\u002F85250435>。\n\n总之，密码A和密码B是这个扇区的访问密码，而控制位用于控制扇区的访问权限，决定拥有密码A或密码B的用户是否可以对扇区进行操作（读取、加值、减值等）。在保持默认的控制位值不变的情况下，在拥有密钥A和密钥B时就能对扇区内的数据进行读写。\n\n### 0扇区\n\n0扇区比较特殊，它的块1-3和其它扇区都一致，但是第0块一般是厂商固化的，不允许修改。我们也可以把它叫作这张卡的“卡号”，每一张卡的第0块都是不一样的。\n\n> 0扇区第0块有16个字节，但是在实际使用中，一般只有前几个字节是有用的，后面的字节即便不一致也不影响，所以有时候“卡号”仅指前几个字节。\n\n### 加密卡与非加密卡\n\n实际上加密卡与非加密卡的概念并不是很准确，因为卡片本身并不能加密数据，而只是通过密码来控制扇区的访问权限。一般我们把密码全部为默认值的卡叫做“非加密卡”，而密码不全为默认值的卡叫做“加密卡”。也就是说，加密卡中至少有一个扇区的一个密码是未知的。\n\n## 如何获取扇区数据\n\n如果需要分析或者复制一张卡片，首先需要获取卡片的数据。这里记录2种方法：\n\n1. 使用Android APP：Mifare Classic Tool，这个工具具备完整的读写功能，包括密码破解的功能也有，如果密码比较弱的话，可以很容易读取到所有扇区的数据\n2. 使用PM5之类的硬件，可淘宝或拼多多，一般会附带软件，具备完整的读写功能\n\n当我们读取到完整的扇区数据后，可以将数据保存到文件中，这样后续分析或者再写入其它地方都比较方便。\n\n## 门禁和复制门禁卡\n\n门禁是非接触IC卡的一个典型应用场景，而视门禁系统的不同，它对于IC卡的使用方式也不完全相同。\n\n- 有些门禁系统只验证IC卡的卡号，而不对其它扇区的数据做进一步的验证，这种一般使用“非加密卡”\n- 有些门禁系统会验证IC卡的卡号，同时会验证某个扇区或某几个扇区的数据，这种一般使用“加密卡”\n\n目前Android手机都标配了全功能NFC，即可以完整地读写非接触IC卡。因此，我们也可以通过手机来复制门禁卡。\n\n使用手机复制门禁卡或者将门禁卡复制到其它卡片上的一般有下面一些解法。\n\n### 手机复制非加密卡\n\n针对“非加密卡”，一般国产手机厂商都内置了“复制门禁卡”的功能，它的原理很简单，就是直接复制门禁卡的0扇区第0块，也就是卡号，针对只验证卡号的门禁系统有效。有一些手机在碰到“加密卡”时，也会尝试复制卡号，此时就会出现门禁卡复制成功，但是刷卡没有反应的情况，因为门禁系统验证其它扇区数据时会失败。\n\n### 在门禁系统中添加新卡\n\n针对“非加密卡”，还有一种解决办法，就是使用手机已有的卡（门禁卡或者公交卡都可以）。这种方法需要联系小区、公司等门禁系统的管理员，让他们在门禁系统中添加新卡，然后将手机已有的卡（门禁卡或者公交卡）刷到门禁系统中，这样就可以使用手机已有的卡来刷门禁了。这也是网上流传的“iPhone复制门禁卡”的方法。\n\n### 手机复制加密卡\n\n针对“加密卡”，可以在手机上尝试破解密码，复制完整的0-15扇区数据，因为所有扇区数据都被完全复制了，因此门禁系统验证时都可以通过。目前已知小米、华为手机都可以通过这样的方式来复制加密卡（可能除了破解之外还有云端共享密码的手段），但是整个过程中，数据对用户而言是不可见的。\n\n也可以只用手机自带功能复制卡号，然后使用另一台带有完整扇区数据的手机或者写卡器将其它扇区写入门禁卡，这样也可以实现复制加密卡。\n\n### 复制门禁卡到手环\n\n针对“非加密卡”，一般来说和复制到手机的方式差不多，只不过是手机上多了一个写入的操作，大部分国产手环搭配Android手机都可以完成这个过程。\n\n针对“加密卡”，同样涉及到破解密码的问题，大部分手环只允许复制卡号。这也是网上很多人觉得手环不能复制加密卡门禁的原因。事实上，在复制卡号之后，手环就拥有了0扇区的数据，接下来只需要再通过手机或者写卡器将其它扇区的数据写入手环即可。\n\n### 复制门禁卡到其它卡片\n\n和复制加密卡到手环类似，首先需要获取原始卡片的扇区数据，然后使用手机或者写卡器写入空白卡片即可。\n\n但值得注意的是，空白卡片分为好几种，有一些是0扇区不可写入的（正常的卡\u002FUID卡），有一些是0扇区可以反复写入的（CUID卡）。如果需要复制门禁卡，则一定需要复制卡号，因此需要使用CUID卡。\n\n> 顺便也提一下“ID卡”，ID卡只存储了卡号，而且卡号一般都刻在卡片上。ID卡的工作频率是125KHz，感应距离大概在10cm左右。如果需要复制ID卡，只需要将卡号告诉给淘宝卖家，他们会帮你复制好卡寄过来。\n\n## 小结\n\n非接触IC卡其实还挺复杂的，但是在门禁这个应用场景里，一般都不会太复杂，要么复制卡号即可，要么复制卡号+扇区数据即可。稍微麻烦一点的是，如果需要复制加密卡，那么就需要破解密码。\n",{"id":40,"type":7,"slug":41,"title":42,"date":43,"category":11,"tags":44,"body_markdown":47,"permalink":18,"excerpt_src":18,"media_type":18,"media_title":18,"media_author":18,"media_url":18,"rating":18,"layout":18,"pv":19,"admin_only":19,"created_at":20,"updated_at":20,"deleted_at":18,"status":21,"cover":48},130,"scripts-to-improve-md-writing","使用脚本改进VSCode Markdown博客编写体验","2022-08-25 08:46",[45,46],"Markdown","图片","\n## 背景\n\n本博客用的是Hexo，最近迁移了很多以前写在别处的文章过来，在迁移过程中发现最不方便的一件事情是图片的处理。\n\n大概的目录结构：\n\n```\n- source\n    - _posts  文章目录\n        - hello1.md  文章\n        - hello2.md  文章\n    - assets  图片目录\n        - hello1  与文章对应的子目录\n            - 01.jpg\n            - 02.jpg\n        - hello2  与文章对应的子目录\n            - 01.jpg\n            - 02.jpg\n```\n\n因为使用的是通用的工具（如VSCode）来编写Markdown，没有专门针对图片资源做额外的处理，因此在写文章的时候如果碰到需要插图，需要经历以下几步：\n\n1. 打开图片目录\n2. 新建与文章对应的子目录\n3. 放入图片\n4. 重命名图片\n5. 在文章中插入图片标记，类似这样`![image 01](\u002Fassets\u002Fhello1\u002F01.jpg)`\n\n其中第1步和第5步都涉及到路径的问题，而且都需要手工进入或者手工输入，当路径比较深或者文章名称比较长的时候，既不方便也容易出错。\n\n\u003C!-- more -->\n\n## 目标\n\n在忍受了这种不方便很久之后，本着“重复的事情一定可以用代码解决”的想法，我决定找到一种更自动化的方式来做。首先，定义一下目标：\n\n1. 图片存入与文章相关的路径\n\n    因为我的图片路径是与文章相关的，因此固定将图片统一放到某个目录的方式不纳入考虑，这就排除了很多现成的解决方案。\n\n    希望这个方案可以自动建立与文章相关联的目录并打开，以便放入图片。\n2. 自动完成重命名\n3. 自动将图片地址填入文章中预留好的位置\n\n## 实现\n\n首先，新建一个脚本放入博客目录中，以便调用：`utils\u002Fassets.js`。\n\n### VSCode调用\n\n当需要插图的时候，对应的文章刚好是VSCode中当前编辑文件，因此希望能将当前文件路径直接传递给脚本。这里使用了一个插件：[Command Runner](https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=edonet.vscode-command-runner)。\n\n这个插件可以通过VSCode配置或者`package.json`来定义一些自定义的命令。于是我在`package.json`中加了这么一段：\n\n```json\n{\n    \"commands\": {\n        \"assets\": \".\u002Futils\u002Fassets.js ${file}\"\n    }\n}\n```\n\n这样就可以通过VSCode直接调用脚本并且传入当前文章的信息。\n\n![command runner 1](\u002Fassets\u002Ftech\u002F2022\u002Fscripts-to-improve-md-writing\u002F01.png)\n\n![command runner 2](\u002Fassets\u002Ftech\u002F2022\u002Fscripts-to-improve-md-writing\u002F02.png)\n\n接下来就是脚本的实现了。\n\n### 新建图片目录\n\n原理比较简单，首先计算一下文章对应的图片目录，然后使用`fs.mkdirSync`新建对应的目录。最后为了方便使用，调用一下系统的`open`命令，把刚新建好的目录打开。\n\n```javascript\nconst fs = require('fs');\nconst path = require('path');\nconst { spawn } = require('child_process');\n\nconst mdPath = process.argv[2];\n\nif(!mdPath) {\n    console.log('md file path required.');\n    process.exit(1);\n}\n\nconst assetsPath = mdPath.replace(\u002F\\\u002F_posts\\\u002F\u002F, '\u002Fassets\u002F').replace(\u002F.md$\u002F, '');\nfs.mkdirSync(assetsPath, { recursive: true });\n\nconsole.log('opening ' + assetsPath);\nspawn('open', [assetsPath]);\n```\n\n> 注意：这个代码实现比较粗糙：\n>\n> 1. 在计算图片目录时粗暴地使用了`replace`来替换，更稳妥的办法是使用相对路径来计算\n> 2. 目录路径未考虑windows系统，这里应该使用`path.sep`更好\n> 3. 未考虑新建目录失败的情况\n> 4. 未考虑没有`open`命令的系统，应该使用封装好的库更好\n\n### 自动重命名\n\n要想让代码为我们放进去的文件自动重命名，首先需要让代码知道“有一个文件被放进去了”，这里就需要使用到`fs.watch`。这个方法可以监听一个目录，当目录发生变化时触发回调，这样我们就可以在回调中完成重命名。\n\n> 注意：`fs.watch`并不是非常好用，在macOS下放入一个文件，会触发`rename`事件两次，不管是事件名称还是触发次数都不太正常。更好的方法是使用`chokidar`这样的库来监听变化。\n\n我这里选择使用数字编号来命名，如果目录中已经有文件存在的话，就需要先知道最新的编号是多少。\n\n```javascript\nlet index = 0;\nconst files = fs.readdirSync(assetsPath);\nfiles.forEach((filename) => {\n    if (\u002F^\\.\u002F.test(filename)) return;\n    if (!\u002F^[\\d]+\\..*$\u002F.test(filename)) return;\n    index = Number(filename.replace(\u002F[^\\d]\u002Fg, ''));\n});\nconsole.log('lastIndex: ' + index);\n```\n\n这段代码首先将序号`index`设为`0`，然后对以纯数字命名的文件进行扫描并解析其数字作为最新的序号。\n\n> 注：这个逻辑也不严谨，如果数字位数不同的话，顺序不是按序号排列的，可能得到错误的结果。但因为我的命令规则全部是2位数字，所以默认这个问题不存在，忽略了。\n\n接下来是监视新的文件并重命名：\n\n```javascript\nconst getNewName = (filename, isPrev = false) => {\n    if (!filename) return filename;\n    const targetIndex = isPrev ? index : index + 1;\n    return filename.replace(\u002F.*(\\..*)$\u002F, (targetIndex + '').padStart(2, '0') + '$1');\n}\n\nfs.watch(assetsPath, {\n    persistent: true,\n}, (e, filename) => {\n    if (\u002F^\\.\u002F.test(filename)) return;\n    if (filename === getNewName(filename, true)) return;\n    setTimeout(() => {\n        try{\n            const newName = getNewName(filename);\n            const oldPath = path.join(assetsPath, filename);\n            const newPath = path.join(assetsPath, newName);\n            fs.renameSync(oldPath, newPath);\n            console.log(filename + ' renamed to ' + newName);\n            index++;\n            \u002F\u002F fillMd(newPath);\n        } catch (e) {\n            \u002F\u002F nothing\n        }\n    }, 500);\n});\n```\n\n这段代码首先定义了一个`getNewName()`方法，用于获取即将被重命名的文件的新文件名。这个方法接受一个`isPrev`参数，它的作用是判断要获取“前一个（已生成）”的文件名，还是“下一个（即将生成）”的文件名。\n\n因为`fs.watch`对新文件和重命名的文件都触发`rename`事件，因此无法区分是被放入了一个新文件，还是之前放入的文件被代码重命名了。这里要做一个判断，将文件名与“前一个（已生成）”的文件名对比，如果相同，说明是刚刚重命名过的文件。\n\n接下来的逻辑比较好理解，就是重命名的过程了，如果成功，就将序号`+1`。\n\n> 值得注意的2个点：\n>\n> 1. `setTimeout`的作用，主要是因为事件会连续触发两次，希望在事件都触发完之后再处理（可能并不是必要的）\n> 2. `try...catch`的使用，也是因为事件会连续触发两次，第2次一定会失败，因此加一个`try...catch`，并不是为了考虑严谨\n\n## 填入Markdown指定位置\n\n在上面的代码中有一个注释的`fillMd()`的调用，它的作用就是将图片地址填入Markdown指定位置。\n\n具体而言，在编写文章时，我会先留一个“洞”，也就是图片标记中的地址是空的：\n\n```\n![图片描述]()\n```\n\n在调用`fillMd()`时会将图片地址填入上述标记中的“洞”：\n\n```javascript\nconst fillMd = (imagePath) => {\n    const mdContent = fs.readFileSync(mdPath, 'utf-8');\n    const imageRelativePath = imagePath.replace(\u002F^.*\\\u002Fsource\\\u002Fassets\\\u002F\u002F, '\u002Fassets\u002F');\n    const newContent = mdContent.replace(\u002F\\!\\[(.*?)\\]\\(\\)\u002F, '![$1](' + imageRelativePath + ')');\n    fs.writeFileSync(mdPath, newContent);\n};\n```\n\n原理也很简单，计算图片的地址并读取Markdown文件，然后通过正则表达式替换的方式将地址填进去，再将新内容写入原文件即可。注意这里的正则表达式没有加`\u002Fg`标记，也就是只替换找到的第一处“洞”，说人话就是一次只填一个，这正是预期的工作方式。\n\n## 效果\n\n有了脚本之后改进的流程：\n\n1. 写文章&留好“洞”（可以写一个就往下处理一个图片，也可以全部写好，最后一起处理）\n2. 运行“assets”脚本\n3. 在自动打开的文件夹中放入图片\n4. 没有然后了\n\n演示视频\u003Chttps:\u002F\u002Ftwitter.com\u002FTooooooBug\u002Fstatus\u002F1562273683246555142>（需要翻墙）。\n\n## 总结和展望\n\n我始终认为，写代码是为了解决问题，至于写得是否完备是否漂亮都是其次的。这个例子其实也是一次“为自己写代码来解决问题”的过程，整个代码只有60行，尽管它不是很严谨，也不是很完美，但仍然算是很满意的一次实践。\n\n如果这个工具继续做下去的话，可能会有几个方向：\n\n1. 做成npm包，提升易用性\n2. 适配更多的文章\u002F图片目录规则，以适应更多的情况\n3. 替换一些工具\u002F写法，提升代码严谨性\n4. 加入更多的功能，比如\n    1. 反过来从md文本中获取图片信息，然后移动\u002F下载保存等\n    2. 加入图床自动上传和替换功能\n    3. ...\n\n不过我只是想把它当成解决自己问题的小工具，因此可能不会再继续花时间深入了，有兴趣的朋友可以参照一下做出更适合自己的工具。\n\n## 附：对Twitter上一些观念的回复\n\n1. 可以用XXX软件：是可以的，但之所以选择Markdown来写东西就是看中了纯文本的通用性，不希望被绑在某个工具上。事实上我也从来不用任何Markdown工具的高级功能，我觉得对纯文本每个字节的掌控是非常必要的。这个脚本也是通用的，你可以手工运行它。\n2. 可以用笔记软件：我认为笔记和文章\u002F博客写作是两种不同的用途。尽管有人喜欢all in one，把文章\u002F资料采集\u002F知识管理\u002F分享\u002Ftodo\u002FOKR等都放入笔记软件，但我不喜欢，我认为笔记就是用来记录的，我自己写的笔记软件也没有任何采集\u002F分享之类的功能，仅仅是输入、记录和搜索而已。\n3. 可以用图床类工具：个人不喜欢将图片和文章分开保存，这些松散的结构间很难建立起联系，在整理的时候会非常困难。此外图床类也会涉及到付费\u002FCDN\u002F防盗链等额外工作。\n\n当然，尽管现阶段不适用于我的文章\u002F博客的场景，但推友们的回复仍然有不少值得学习的地方，也有不少好工具，值得收藏。\n","https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Ftech\u002F2022\u002Fscripts-to-improve-md-writing\u002F01.png",{"id":50,"type":7,"slug":51,"title":52,"date":53,"category":11,"tags":54,"body_markdown":57,"permalink":18,"excerpt_src":18,"media_type":18,"media_title":18,"media_author":18,"media_url":18,"rating":18,"layout":18,"pv":19,"admin_only":19,"created_at":20,"updated_at":20,"deleted_at":18,"status":21,"cover":22},131,"talking-about-code-style","关于代码风格","2022-08-19 18:00",[55,56],"代码风格","代码规范","\n> 2016年的旧文，之前未发表，时隔6年后整理一下发出来。\n\n如果从我写下第一行代码开始算的话，我写代码的历史大概已经超过20个年头了。这20多年里接触过各种各样的语言，也接触过各种各样的代码风格。当然也见证了很多代码风格引起的撕逼大战。\n\n对于代码风格这种事情上的架，我一般不参与吵的，觉得是一件很无聊的事情。直到今天（2016年）打开微博看到几十条评论，有点蒙圈。\n\n事情的起因是上周我在微博上发了一个条吐槽：\n\n> 最近面试好多人连12345这五行代码的执行顺序都讲不清楚。就算不知道5我也忍了，好多人连1234这四行代码是什么顺序跑的都搞不清楚。现在前端门槛低到这样的程度了么？（为了把它们排到第12345行上，刻意调整了代码格式，请轻拍。）\n\n附上了一段代码：\n\n```javascript\nfor(var i=0;\n\t\ti\u003C3;\n\t\ti++){\n\tsetTimeout(function(){\n\t\tconsole.log(i)\n\t},0)\n}\n```\n\n然后今天收到了一堆吐槽：\n\n- “花括号不换行，烧死”\n- “等号两边不换行，烧死”\n- “逗号右边没加空格，烧死”\n\n在微博上也零碎地做了一些回复，不过还是觉得没有把想说的话说完，于是有了这篇。\n\n\u003C!-- more -->\n\n## 什么是代码风格\n\n具体一点说，代码风格即是代码长成什么样子。哪些因素会决定代码长成什么样子呢？\n\n- 换行符\n- 空行\n- 空格\n- 缩进\n- 分号\n- 括号\n- ...\n\n自然，决定代码长成什么样子的还有很多因素，比如：\n\n- 变量声明在一行还是每行一个\n- 变量声明在函数最前面还是使用的地方\n- 函数声明还是函数表达式\n- 用`if`还是用`switch`\n- 写成三个函数还是一个函数\n- 分模块还是一个文件到底\n- 面向过程还是面向对象\n- ...\n\n虽然这一些因素也会决定代码长成什么样子，但是同时，它们也决定了代码的其它部分，比如结构是否容易理解和维护，性能是否足够好……简单说，这些会决定风格，但决定的并不只是风格。\n\n所以本文的想说的仅限于前面说的那一堆。\n\n## 代码风格的重要性\n\n好的代码风格到底有什么作用呢？为什么如此多人强调代码风格呢？一般可以找到如下答案：\n\n- 避免bug\n- 方便阅读\n- 便于维护\n- 提高代码编写效率\n- 方便团队协作\n\n然而仔细想一想，仅仅靠我们上面所说的“风格”，这些目标似乎都无法达成。所有这些目标都需要同时依赖其它的工程手段或者流程，才有可能达到一部分效果。甚至可以说代码风格对于这些目标来说是不太重要的。\n\n## 我和代码风格的战斗史\n\n一方面，基于上面的怀疑而产生的结论，一方面，也开始接触各种各样的代码风格，有加空格的，有不加空格的，有喜欢加很多空行把代码写松散的，也有一行空行都没有让结构很紧凑的，有tab缩进的，有2空格缩进的，有4空格缩进的，有分号党，有不加分号党的……\n\n这些代码中有很好理解和维护的，也有不太好理解和维护的，但无论如何，都很难得出代码风格与可维护之间有直接关系的结论。如果一定要说的话，只能说：能把代码写得易于理解和维护的，大概率代码风格也是统一的。\n\n基于这样的认知，我开始不拘泥于代码风格的限制，在不同的项目中会使用不同的风格（在同一个项目中还是会使用同一种风格，并使用工具严格约束），心情好时加加空格，心情不好时不加空格。发现这些事情中，除了在没有注释的情况下用于逻辑分段的空行以外，其它的其实对代码编写和阅读几乎没有影响。\n\n但……上面所说都限于个人项目。在团队作战中风格仍然要保持统一，而原因，可能更出乎意料，只是为了让大家的IDE不要乱改代码造成diff困难。\n\n## 小结\n\n代码风格是个严肃的事情，但将它上升到非常高的高度则大可不必。不关注代码风格的不能叫程序员，只关注代码风格的不能叫好的程序员。\n\n> 本来有一节中“除了代码风格，程序员更应该关注什么”，但是2016年没有写，就不再补了。\n",{"id":59,"type":7,"slug":60,"title":61,"date":62,"category":11,"tags":63,"body_markdown":66,"permalink":18,"excerpt_src":18,"media_type":18,"media_title":18,"media_author":18,"media_url":18,"rating":18,"layout":18,"pv":19,"admin_only":19,"created_at":20,"updated_at":20,"deleted_at":18,"status":21,"cover":22},128,"why-weixin-can-only-login-via-cellphone","为什么微信的登录一定需要手机","2020-02-24 14:17",[64,65],"微信","手机","\n来自知乎的问题，原地址\u003Chttps:\u002F\u002Fwww.zhihu.com\u002Fquestion\u002F270040312\u002Fanswer\u002F1034679430>。\n\n先说结论：微信是一个移动端通讯工具，再通俗一点，它就是一个手机软件。至于电脑版什么的，就是随手一做，如果威胁到手机版的地位，关掉都不会有人心疼的。\n\n知乎确实迎来新一辈的朋友了，似乎已经没有多少人知道微信诞生的背景，也没有人在乎背后的逻辑了。微信在知乎上的口碑也从一开始的神作，变成了最被唾弃的软件。\n\n先问几个问题：\n\n1. 微信10亿左右的月活用户（最近没翻财报，但大约是这个数字，量级不会错），你们猜有没有1亿人使用PC微信？\n2. 为什么你一边骂着微信，一边却离不开微信？\n\n\u003C!-- more -->\n\n## 移动互联网大潮和“船票”论\n\n大约在2013年，所有的互联网大厂都意识到，移动互联网要来了。伴随移动互联网的而来的，则是各个大厂无尽的恐慌感，因为害怕一招不慎就被拍死了。彼时大家都在寻找各自在移动互联网的杀手级APP，则腾讯的马化腾则将之称之为移动互联网的船票。\n\n回头来看，这个论断是非常有前瞻性的。2013年之前的互联网产品，到现在还存在的已经不多了，即使存在，也已经是一个全新的移动APP的形态了。360电脑管家、QQ影音、暴风影音、千千静听、光影魔术手、PPS……当年的装机必备，希望现在还有记得它们的名字……\n\nQQ诞生于1998年，就地位而言，是当之无愧的PC互联网王者。但是在移动互联网大潮来临之前，手机QQ和PC QQ竟然都不是在同一个事业群（彼时还叫业务线），可想而知手机QQ的地位和状态是如何。光是从版本这一个侧面就能看出来：QQ手机版、手机QQ、手机精简版、轻聊版……（这些版本名称不一定完全准确，但当时确实是有三到四个不同的版本。）所以当时尽管手机QQ的下载量非常高，但是在产品上它仍然是一个非常彻底的PC first的产品。\n\n于是，大家都在寻找针对移动互联网形态的王牌产品。对腾讯而言，很幸运的就是，微信帮腾讯拿到了移动互联网的第一张船票。\n\n## Mobile Only\n\n(用英文作标题不是装逼，而是业内习惯这样称呼，mobile\u002Fpc only\u002Ffirst。)\n\n当时的QQ有一个现实的情况：你要QQ找人，需要先看他在不在线。当他不在线，你的选择是：1. 在吗？（你隐身了吗？）2. 发短信\u002F打电话。\n\n再对比一下今天的微信，你要发消息，是否会先问一句“在吗”？（如果是的话，大概是知乎上最受人讨厌的人之一吧。）也就是说，你已经默认了，我发微信，他一定会收得到，最起码不会像QQ一样，可能大半个月之后才看到。所以你为什么一边骂着微信一边离不开微信，因为大家都知道微信消息的触达率高，因为大家都用微信，你不得不用微信。\n\n这个差异并不是天然形成的，而是微信长期以来的产品策略造成的结果。\n\n微信从一开始就希望自己是纯粹的移动端软件，因为张小龙坚信，手机是人的延伸，而电脑不是。现在回头看，这个判断是非常正确的。你想想有什么东西能让你在醒着的时候一直不离手，唯有手机。这样来看，一款通讯软件，能让人只能通过手机找到我，和能让人通过电脑或手机找到我，消息触达率是完全不同的。如果微信是一个PC和mobile并重的软件，你可以手机也可以电脑登录，那么你会面临和QQ完全一样的情况：我需要思考我发的消息能不能被看到。如果是这种情况，微信完全不可能有今天全面替代短信甚至电话的局面。\n\n所以web版微信一开始叫作“连接键盘”，它只是帮你打字的一个工具，不是微信。所以电脑版微信一直姗姗来迟而且功能不全，正如开头说的，如果它被关掉，都不会有人心疼的。\n\n所以微信永远不会提供电脑端独立登录。因为电脑端是依托移动端而存在的，当你的手机不在身边时，对不起，你不能使用微信。这和你没有手机时不能发短信、打电话是一样的道理。（iPhone用户别扛，背后的思路是一样的。）微信希望，如果你不能通过微信找到对方，理由只能是手机没带\u002F手机没电，而不是“我手机没登，我只在上班的时候电脑上用微信”。\n\n## 微信的好用与不好用\n\n微信在知乎的口碑全面下滑，最被诟病的就是不好用：传文件不行，群管理不行，聊天列表管理不行，消息同步不行……\n\n所有的这些问题真实存在，并且当你碰到的时候，真的很痛。但是张小龙说，每天有1亿人教我做微信。言下之意，你们懂个屁。这到底是怎么一回事，我试着做一些猜测。\n\n有一些功能的设计，是故意为之。举两个稍微证据充足一些的例子吧：\n\n### 群\n\n在微信群之前，群聊的主战场是QQ群，但是QQ群在以前是非常严格的限制的，每个人只能建3个群（随等级提升而提升），群成员有限制，从100到1000人不等。每个群都有群号，建一个就要占用一个建群名额。QQ群有群主，有管理员，有正式的公告，有群文件等等……简单来说，QQ群的建立是需要非常正式的考虑的，三思而后行。\n\n但是微信群一推出就打破了这一限制，随便建群，可以从通讯录建，也可以当面建群（这个功能是后加的）。群也没有什么管理员，大家都可以改群名。\n\n这么一对比就发现，QQ群适合组织，而微信群，适合平等而非正式的交流。\n\n### 消息同步\n\n微信的确有了电脑端，也有了iPad端，理想情况下，你的确可以在三个左右的终端同步登录同一个微信，但是只要某个端不在登录状态，消息是不会同步的。（事实上一开始PC端是完全不同步的，现在可以勾选同步最近消息，勉强同步一部分。）\n\n微信在非正式场合有给出过一些解释：同步会带来认知上的麻烦，因此选择不同步。而这个认知上的麻烦是什么呢？就是你得时刻记着我的哪个设备上同步有登录，这些消息都会同步过去。而如果你忘记这一点，就有可能带来隐私泄漏，造成更多的麻烦。（参考男\u002F女朋友互相登录QQ查看聊天记录。）\n\n还有更多的功能，微信在设计的时候都有过一些很慎重的考虑，但是因为证据没有那么明确，说多了有些洗地嫌疑，就不再列举。\n\n但是核心的意思是，微信的功能设计有其场景，我个人觉得非常明显的两个点：私人会话、简化认知。\n\n为什么老人觉得微信好用，其实就是老人的使用正好符合这两个点：微信里基本都是家人和朋友，都是私人会话；对手机不是很精通，甚至对界面元素的识别都有困难，因此认知越简单越好。\n\n而知乎的朋友觉得微信不好用：因为都是工作群，都是工作场景，需要更精细的消息同步\u002F消息提醒\u002F文件传输；因为都是高知人群，界面上差一个像素都能看出区别，因此觉得微信有些过于简陋。\n\n这些不是微信的错，也不是你的错，而是你的需求和微信的定位产生了冲突。\n\n## 10亿人的微信\n\n微信是一个面向十多亿用户的软件，而关注微信使用体验的知乎用户有多少呢？这十多亿用户和知乎用户的画像（人在美国、刚下飞机、年薪百万）又差多少呢？有多少人会想到“大字体”是微信的一个刚需？有多少人会想到微信支付输完密码后询问是否开启指纹支付的弹窗对大量的人群是一个困扰（因为没有人告诉他应该选开通还是不开通）？\n\n回到问题，十多亿人里，又有多少人需要电脑端微信呢？\n\n所以，我的看法：\n\n1. 电脑端微信登录时需要手机扫码，没毛病，因为它是一个纯手机软件。（但是除了微信之外，其他的这么操作我不能理解，典型的比如企业微信。）\n2. 微信\u002F电脑端微信的种种难用之处，同样感同身受。但是给10亿人做软件和给知乎高知群体做软件，做法本来就不一样了，同为互联网从业者，表示理解。\n3. 如果有可能，尽量选择场景合适的工具做合适的事情，放过自己，也放过微信。\n",{"id":68,"type":7,"slug":69,"title":70,"date":71,"category":11,"tags":72,"body_markdown":75,"permalink":76,"excerpt_src":18,"media_type":18,"media_title":18,"media_author":18,"media_url":18,"rating":18,"layout":18,"pv":19,"admin_only":19,"created_at":20,"updated_at":20,"deleted_at":18,"status":21,"cover":77},127,"remove-sensitive-data-from-git-with-rebase","使用Rebase操作抹去Git仓库中的敏感信息","2019-11-29 20:38",[73,74],"Git","Rebase","\n使用Git的时候，有时候会碰到需要从Git仓库中永久“抹除”某些敏感信息的情况。例如不小心提交了密码之类的信息到仓库，此时只抹掉这些信息重新提交是没有用的，因为其他人仍然可以通过Git历史看到这些敏感信息。因此需要一种方法将这些信息彻底从仓库中抹去。\n\n如果去网上搜索的话，能很容易找到使用`branch-filter`来处理的方法，例如\n\n```sh\ngit filter-branch --tree-filter \"find . -name '*.*' -exec sed -i '' -e 's\u002FOLDSTRING\u002FNEWSTRING\u002Fg' {} \\;\" -f\n```\n\n写法有很多种，但是思路都差不多，就是遍历一遍所有的提交，对这些提交执行指定的命令（例如用`sed`替换指定的内容，或者移除相关文件），然后重新生成新的提交和分支。\n\n不过这种思路对于我来说却不太受用，原因有几个：\n\n1. 命令行掌握不太好，看到这种命令都不太认识，完全不敢直接放在项目中去跑\n2. 直接进行字符串级别的替换，在某些情况下不够用，例如想通过更复杂的编辑手段（新增文本、修改文本、删除文本同时操作）抹除敏感信息\n3. 直接对整个仓库\u002F整个文件进行字符串级别的替换还是有些不放心，毕竟要修改的部分是明确的，却无法明确地指定这个命令只修改这一部分信息\n\n那怎么办呢？其实在这种场景下，也可以尝试使用`git rebase`来解决问题。\n\n## rebase是干什么的\n\n`rebase`顾名思义，就是重新确定一个提交（一个分支）的“基”，这个“基”就是指它的祖先元素。具体的做法是，首先将提交退回到“基”所在的点，然后将之前做过的提交在这个“基”的基础上重复做一遍。相当于修改了当前分支衍生出来的基础，因此中文也被译为“变基”。\n\n\u003C!-- more -->\n\n还是举个例子：\n\n新建一个仓库，然后做两次提交`A1`、`A2`：\n\n```sh\n# 初始化\nmkdir test\ncd test\ngit init\n\n# 两次提交\necho \"A1\">>1.txt\ngit add .\ngit commit -m A1\n\necho \"A2\">>2.txt\ngit add .\ngit commit -m A2\n```\n\n接下来分成两个分支，分别进行提交`B1`、`B2`和`C1`、`C2`：\n\n```sh\n# 创建新分支\ngit checkout -b new\n\n# 新分支提交B1 B2\necho \"B1\">>1.txt\ngit add .\ngit commit -m B1\n\necho \"B2\">>2.txt\ngit add .\ngit commit -m B2\n\n# 切换主分支\ngit checkout master\n\n# 主分支提交C1 C2\necho \"C1\">>1.txt\ngit add .\ngit commit -m C1\n\necho \"C2\">>2.txt\ngit add .\ngit commit -m C2\n```\n\n于是得到一张这样的分支图：\n\n![分支图](\u002Fassets\u002Fremove-sensitive-data-from-git-with-rebase\u002F01.png)\n\n此时`new`和`master`两个分支分别指向`B2`和`C2`两次提交，而他们的共同祖先（即前文中说的“基”，这个说法不严谨，仅为理解），则是`A2`这次提交。此时我们就可以用`rebase`来改变其中某一个分支的走向。例如，让`C1`在`B2`的基础上修改，即让`master`分支的提交顺序变成`A1-A2-B1-B2-C1-C2`：\n\n```sh\n# 对哪个分支rebase就切换到哪个分支\ngit checkout master\n\ngit rebase new\n```\n\n而此时就出现了冲突，因为`C1`和`B1`两次提交都对`1.txt`做了修改。\n\n```sh\nA1\n\u003C\u003C\u003C\u003C\u003C\u003C\u003C HEAD\nB1\n=======\nC1\n>>>>>>> C1\n```\n\n我们选择手工解决冲突，将`B1`放前面，`C1`放后面，解决完之后继续`rebase`\n\n```sh\n# 用add标记解决完冲突\ngit add 1.txt\ngit rebase --continue\n```\n\n此时分支图会变成这样，表明C1这次提交已经变基成功。但同时也能看到C2在变基的时候也产生了冲突。\n\n![分支图](\u002Fassets\u002Fremove-sensitive-data-from-git-with-rebase\u002F02.png)\n\n这里和上面一样处理即可。完成后就能看到变基后的分支图。\n\n![分支图](\u002Fassets\u002Fremove-sensitive-data-from-git-with-rebase\u002F03.png)\n\n需要说明的是，尽管提交的描述信息（commit message）没变，但是`C1`、`C2`这两次提交实际上是新产生的，因此它们的commit id和之前的提交是完全不同的。\n\n通过这个例子，能清楚地看到`rebase`命令的作用，即改变提交（分支）的基础。一般来说，在多人协作过程中，适合将同一分支互相拉取变更的操作使用`rebase`来完成，这样可以保持同一分支的提交历史是线性的，方便回溯。\n\n## 使用rebase改变Git历史\n\n在上面`rebase`的例子中，还有一个点值得注意，以`C1`这次提交为例，`rebase`前后两种情况下，虽然都是在`1.txt`结尾添加`C1`这行文字，但是基础和结果都是不同的。在`rebase`之前，`1.txt`的内容是由`A1`变成`A1\\nC1`，而在`rebase`之后则是由`A1\\nB1`变为`A1\\nB1\\nC1`。\n\n可见在`rebase`的时候，不止是提交的父节点（“基”）会变，文件内容也有所变化。而我们之前在`rebase`时面临的冲突，也正是因为这个变化所带来的。但同时，正因为有这样一个变化，使得我们有机会通过`rebase`的方式来永久改写Git仓库中某一个文件的历史。\n\n仍然看上面的例子，现在只看`rebase`之后的情况。在`C1`这次提交中，我们为文件`1.txt`在结尾处添加了内容`C1`。假设这个`C1`是一个很敏感的信息（例如密码），我们要如何将它从仓库历史中抹去呢？\n\n![分支图](\u002Fassets\u002Fremove-sensitive-data-from-git-with-rebase\u002F04.png)\n\n首先我们在C1提交之前找到一个点，例如B2，然后基于它新建一个分支。（例如`new`这个分支。）接下来在这个分支上，对文件`1.txt`进行修改，例如我们增加一行`C2`。即`1.txt`内容变为\n\n```\nA1\nB1\nC2\n```\n\n并进行一次提交。\n\n![分支图](\u002Fassets\u002Fremove-sensitive-data-from-git-with-rebase\u002F05.png)\n\n接下来，我们对C2这次提交（即`master`分支）进行`rebase`操作：\n\n```sh\n> git checkout master\n> git rebase new\n```\n\n此时git会告诉我们，产生了冲突。\n\n```\nFirst, rewinding head to replay your work on top of it...\nApplying: C1\nUsing index info to reconstruct a base tree...\nM\t1.txt\nFalling back to patching base and 3-way merge...\nAuto-merging 1.txt\nCONFLICT (content): Merge conflict in 1.txt\nerror: Failed to merge in the changes.\nPatch failed at 0001 C1\nhint: Use 'git am --show-current-patch' to see the failed patch\n\nResolve all conflicts manually, mark them as resolved with\n\"git add\u002Frm \u003Cconflicted_files>\", then run \"git rebase --continue\".\nYou can instead skip this commit: run \"git rebase --skip\".\nTo abort and get back to the state before \"git rebase\", run \"git rebase --abort\".\n```\n\n冲突内容正是原来的`C1`和我们刚在新分支上添加的`C2`：\n\n```\nA1\nB1\n\u003C\u003C\u003C\u003C\u003C\u003C\u003C HEAD\nC2\n=======\nC1\n>>>>>>> C1\n```\n\n前文假设，`C1`是我们要删除的敏感信息，因此此时手工解决冲突，将`C1`删除，留下`C2`。然后`git rebase --continue`即可。\n\n可以看到我们的提交记录变成了这样：\n\n![分支图](\u002Fassets\u002Fremove-sensitive-data-from-git-with-rebase\u002F06.png)\n\n敏感信息被彻底删除了。\n\n## 小结\n\n上面只是展示了一种简单的情况，但已经足够说明使用`rebase`来删除代码库中敏感信息的核心思路和关键步骤了。\n\n有一些值得注意的细节：\n\n1. 由于C1提交和重写C1的提交都只修改了一行代码，因此在`rebase`过程中，把这一行的冲突解决完，并且`git rebase --continue`时，会提示没有变更（因为唯一的变更在冲突解决过程中被编辑好了），此时需要使用`git rebase --skip`跳过这次提交。\n2. 上面我们是使用`rebase`操作时，编辑冲突的时机来编辑代码文件，从而将`C1`这个敏感信息删除的。如果无法保证一定产生冲突，则可以使用`git rebase -i`（交互式变基）来手工指定需要对哪些提交进行编辑，从而在不一定有冲突时，也有机会编辑代码文件，来将敏感信息删除。关于交互式变基，可参考网络上相关文档。\n3. 如果敏感信息在第一次提交就被带入版本库了，则上面说的“在C1提交之前找到一个点”无法完成。此时可以用`git checkout --orphan branch-name`来创建一个完全空白且没有父节点的分支，并且将当前分支的提交基于这个新的空分支来进行`rebase`，从而获得编辑代码删除敏感信息的机会。\n\n最后，一个提醒：不论用什么方法来修改版本库历史，都是在重写历史，虽然看起来提交的commit message是一样的，但是却是完全全新的提交和分支发展路径。当推送到代码库时，需要使用`git push --force`来强制推送，其他人则需要使用`git pull --rebase`来重写本地分支。\n","\u002Farticle\u002Fremove-sensitive-data-from-git-with-rebase.html","https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Fremove-sensitive-data-from-git-with-rebase\u002F01.png",{"id":79,"type":7,"slug":80,"title":81,"date":82,"category":11,"tags":83,"body_markdown":86,"permalink":18,"excerpt_src":18,"media_type":18,"media_title":18,"media_author":18,"media_url":18,"rating":18,"layout":18,"pv":19,"admin_only":19,"created_at":20,"updated_at":20,"deleted_at":18,"status":21,"cover":22},126,"should-involve-design-as-a-developer","【问答】如何看待“代码没有写到10万行不要碰设计”这样的观点？","2018-07-29 10:45",[84,85],"开发","设计","\n本文来自知乎问题[如何看待“代码没有写到10万行不要碰设计”这样的观点？](https:\u002F\u002Fwww.zhihu.com\u002Fquestion\u002F287006602\u002Fanswer\u002F454886392)\n\n有些答主可能误解了题主说的“设计”的意思。这里不是指代码设计或者工程设计，指的是交互和视觉设计。\n\n## 1 前端需要对交互和视觉设计有一些认识，这是完全没有问题的\n\n比较硬的关系是，你会接设计同学出的设计稿，如果一点不了解设计领域的知识，拿到稿件会手足无措。适当地了解设计理论、方法、软件使用以及设计稿的常用处理（蒙版、切图、切片、变换、拼接等），是前端必备的工作知识。\n\n比较软的关系是，前端是离用户最近的工程师，需要对用户体验负责。很多时候设计稿输出来的是静态的，但是用户交互的是一个动态的页面，如何把这些交互做好，设计和前端基本上各有一半的能力和责任。\n\n\u003C!-- more -->\n\n## 2 分清楚输入和输出\n\n很多新人弄不清楚自己能力的输入和输出是什么关系。所谓输入就是学习，你的代码能力、设计能力、审美的学习培养，这些都是输入。把你的代码能力、设计能力和审美应用到工作中，输出好的产品，这是输出。\n\n我个人认为，公司的工作是没有义务为输入负责的，更多的是需要输出。换句话说，你的学习、培养，不应该由公司的工作来承担，公司为你付工资，是希望你能输出自己的能力，为公司的产品添砖加瓦、锦上添花。\n\n因此这就很容易理解了，你作为一个不会设计的前端，要去参与交互设计的过程，这对你来说是一个输入的过程，是你学习怎么做交互的过程。对于公司的工作来说，这很可能是一个负效率的事情。因此，公司\u002Fleader不予答应是天经地义的，不要有什么抱怨。\n\n如果真的觉得自己需要学习锻炼，可以放到业余时间去学习。你总会有上下班的时间，有休息的时间，有项目间空档的时间，用这些时间去学习和锻炼，甚至与你们项目的产品、设计同学去探讨设计、实现上的细节，探讨用户体验、用户价值等。这是一个更合理的方式。\n\n## 3 借力公司资源\n\n如上一段所说，让不专业的人参与专业的工作，对公司来说不一定是好事。但是公司总还是有专业的资源，包括设计的流程、设计的资源库、方法论积累、规范以及专业的设计师同学。这些都是学习过程中非常难得的资源，可以先静下心了解、学习。甚至在自己的工作时间允许的时候，参与设计评审等环节，但只能以旁听、学习者的身份参与。如果真的可以的话，经过一段时间积累，会有比较大的提升。\n\n## 4 注意专业深度和广度的关系\n\n上一段说的借力公司资源，理论上是可行的。但是实际操作中，可能引发一些问题，比如Leader会担心你是否不想在开发上做深入学习和实践了，是否想转设计岗，甚至是否是想离职。\n\n所以更为重要的是，要认清自己的身份，你首先是一个工程师，需要对代码负责。接下来才是一个前端工程师，大部分对代码负责，少部分对用户、交互、体验负责。（如果公司\u002F项目的要求没那么高，那只对代码负责也可以的。）因此，所谓“代码没有写到10万行不要碰设计”，本质上是希望你首先打好作为工程师的基础，把手上的代码写好。先学会好好写代码，再去学设计。\n\n这其实也就是常说的专业深度和广度的关系。你说前端的基础不包括设计知识吗？当然也包括，第一段就说了。但是在一个人入门还不深入，工作刚能上手的阶段，更好的学习路径必然是先把更为基础的代码学好。设计作为前端进阶甚至外延的知识，在打好第一阶段的基础之后再去了解学习更好。这个道理其实也适用于其它领域的知识，比如对前端来说，后端、客户端、测试等，都重要。但是没有这些知识，你同样能干活，但是没有前端基础，你干不了活。\n\n从公司的角度来说，招你过来，也是为了让你输出前端方面的能力，而不是培养你的设计知识，否则为什么不招设计师呢？可能还更便宜一些。也就是说，你的价值首先是100%的前端，然后才可以叠加10% 20%的设计，这属于你在前端之外为公司创造的额外的价值。如果你是50%的前端 + 10%的设计，还有一堆的前端问题都搞不定\u002F搞不好，那公司可能更希望招一个80%但不会设计的前端。不管是在自身学习还是在参与公司工作、解决产品问题，都要非常清醒地认识到这一点。\n\n所以个人建议的学习通道是，好好学前端知识，好好码前端代码。先把知识学深，觉得技术上基本上没有太大的难题，什么问题都有解决方案了，再去追求更广的领域，比如产品、设计、用户体验等等。\n",{"id":88,"type":7,"slug":89,"title":90,"date":91,"category":11,"tags":92,"body_markdown":94,"permalink":18,"excerpt_src":18,"media_type":18,"media_title":18,"media_author":18,"media_url":18,"rating":18,"layout":18,"pv":19,"admin_only":19,"created_at":20,"updated_at":20,"deleted_at":18,"status":21,"cover":22},124,"comments-on-tnt-workstation","【问答】如何评价锤子科技推出的TNT工作站？","2018-05-16 09:46",[93],"tnt","\n本文来自知乎问题[如何评价锤子科技推出的TNT工作站？](https:\u002F\u002Fwww.zhihu.com\u002Fquestion\u002F277297661\u002Fanswer\u002F392829702)\n\n先说三个革命性的交互。\n\n## 一，TNT\n\n> TNT是指touch and talk，先指点再说话，通过这两个动作的组合完成一个输入动作。\n\n我个人认为这个交互方向还是有点想法的，先指点再用语音确实能极大提高机器理解的准确率。\n\n但是具体的交互和场景是不是这个工作站这么玩，值得打问号。\n\n先说交互，从现场演示和演示视频来看，它需要人频繁地“点按屏幕-说话-放开”，这个过程其实很容易失误，现场演示也有这样的失误（希望没看错）。另外长时间办公的场景下，反复高频进行这个操作，手是否会累，我个人觉得是很有可能的事情。\n\n再说场景，很多答案都提到了，这玩意是放办公室？一堆人靠吼来办公？靠语音这么不靠谱的方式来做excel表格真的好？还是放老板那？让老板自己记账、做PPT？\n\n如果有个财务是这么做账：“5月 工资 10000 报销 1000 老板出去玩 500 啊 识别错了 五百 不是五十” 会不会被打死？发布会完了之后回想起这个表格的演示，只能说，锤子的产品经理有认真地研究过靠excel为生的人是怎么玩键盘和excel的吗？\n\n## 二，水晶球\n\n> 水晶球是指AI能力的应用，发布会演示最多的场景是PPT自动排版、寻找素材之类的场景。\n\n这一块个人觉得是整个发布会最大的亮点了，确实能解决一些痛点。\n\n人工智能、AI这么火，但是却真的没有见到在PC上有什么用武之地，一堆厂商都钻到了语音助手的牛角尖里出来不来，包括Windows的小娜、Mac的Siri，反正我是没见有人对着电脑说话的。\n\n所以这个水晶球实际上算一个初级AI在PC上的落地实践，而且确实是能在一定程度上提升工作效率的。\n\n乐观一点想，就算这是锤子给大家做了个示范吧，AI不一定要是语音助手。也希望大家去尽情地抄一下这个特性，把AI真的带到PC电脑上来。\n\n如果只是讨论水晶球在这个工作站的事情的话，其实还任重而道远。如果功能仅限于PPT排排版，搜搜图什么的，那么用途会非常有限。如果能扩展到整个系统层面，就会比较好玩了。但是在系统层面去做这些事情，就意味着要打通各个APP，要有统一的标准和接口，凭锤子一己之力去推动这个事情，不看好。\n\n## 三，发牌？卡牌？\n\n不记得名字了，反正就这么个东西。个人打负分。这玩意看起来很炫，但是没有人真的会这么用。\n\n先看现场演示的结果，操作失误率非常高。这背后并不一定都是老罗的问题，而很可能是这套交互本身的问题。\n\n以IM为例，将10+个长得一毛一样的IM对话一起摆在人眼面前，信息处理的压力会非常大。人要非常小心地去辨别和记忆每个窗口的对象、会话上下文，稍不留神就会出错。即使你很小心不出错，也会处于一种“我一不留神就会出错”的焦虑中。\n\n如果你说，我不用记忆啊，我一个一个去看不就好了。对，那你为什么不一个一个展示给我呢？这两者在信息的传达上完全不一回事的。\n\n至于后半程演示的建群，批量单聊等，其实是交互成本很高的操作。得承认它很炫，但是因为交互成本高，实用性其实并不会太好。完全属于只是看着爽的东西。\n\n## 定位\n\n再说这个工作站的定位。\n\n第一个问题，我用什么写代码？\n\n同样的问题，可以换成“我用什么做音频\u002F做视频\u002F做实验\u002F做科学计划\u002F做机器学习\u002F......”，本质上是一个生态的问题。既然是作为桌面工作站，那么它的功能完备性就显得非常重要了。如果只能Office三件套，再七拼八凑地用一些不知名的软件去勉强把工作应付出来（是否能应付还不一定），那怎么能叫工作站？这样的状态只有在“我在地铁\u002F公交\u002F飞机等条件非常受限的地方，需要临时完成一点微小的工作”的场景下，才能接受的。\n\n所以，拿一个Android来做工作站，是一件相当不被认可的事情。\n\n第二个问题，我凭什么不买iMac\u002FSurface Studio\u002F笔记本？\n\n至少这一些是真的电脑，想做的事情都有成熟的解决方案来做，不用担心如果我买了它有没有能做事的软件，做得爽不爽。\n\n如果你是一个价格2000块的工作站，那我还有理由说服自己，人家比你们这些货便宜好几倍啊，试试又不会死。\n\n但是一台手机 + 一台电脑，一万多？给我一个不买iMac的理由？哪怕就一个。\n",{"id":96,"type":7,"slug":97,"title":98,"date":99,"category":11,"tags":100,"body_markdown":103,"permalink":18,"excerpt_src":18,"media_type":18,"media_title":18,"media_author":18,"media_url":18,"rating":18,"layout":18,"pv":19,"admin_only":19,"created_at":20,"updated_at":20,"deleted_at":18,"status":21,"cover":104},123,"2017-tech-hot-points","2017年科技新闻热点分析","2018-03-05 13:25",[101,102],"科技","热点","\n2017年开始我做了一款资讯App，叫土葱日报，主要用来抓取科技新闻。目前只有Android版本，在Google Play和应用宝市场有下载。\n\n经过一年的运行，积累了上十万条新闻数据，并从中分析出了近百万关键词数据。在此对结论做一个展示。\n\n\u003C!-- more -->\n\n![五大热门公司](\u002Fassets\u002Ftech\u002F2018\u002F2017-tech-hot-points\u002F01.jpg)\n\n五大热门公司：苹果、腾讯、亚马逊、乐视、特斯拉\n\n![五大热门技术](\u002Fassets\u002Ftech\u002F2018\u002F2017-tech-hot-points\u002F02.jpg)\n\n五大热门技术：人工智能、VR AR、小程序、自动驾驶、区块链\n\n![几个结论](\u002Fassets\u002Ftech\u002F2018\u002F2017-tech-hot-points\u002F03.jpg)\n\n注：以上皆由土葱日报采集的科技新闻分析而来，由于分析方法不一定严谨，数据不一定全面，不对结论负责任。\n\n注：由于文字不足300字，无法声明原创，但本文结论均由本人原创。\n","https:\u002F\u002Fassets.toobug.net\u002Fassets\u002Ftech\u002F2018\u002F2017-tech-hot-points\u002F01.jpg",{"id":106,"type":7,"slug":107,"title":108,"date":109,"category":11,"tags":110,"body_markdown":114,"permalink":18,"excerpt_src":18,"media_type":18,"media_title":18,"media_author":18,"media_url":18,"rating":18,"layout":18,"pv":19,"admin_only":19,"created_at":20,"updated_at":20,"deleted_at":18,"status":21,"cover":22},125,"hello-xiaoai","小爱同学 你好吗","2018-01-09 14:07",[111,112,113],"智能音箱","小爱同学","小米","## 秀优越感\n\n小米AI音箱299元，但是现在还买不到。要不选择等半年，要不选择淘宝加价一两百。然而令人惊喜的是，参加前端体验大会后，主办方直接送了一个作为礼品。于是我能抢先体验到这一款产品。秀优越感完毕。\n\n## 功能\n\n外形和基本功能就不详细介绍了，网上的评测文章挺多了。\n\n我用得比较多的功能主要还是点歌，叫一声小爱同学，然后就可以点歌。而且还可以沿同一歌手的歌单一直放下去。至于音效嘛，作为一款身型小巧的音箱，还是有点超出预期的。\n\n此外，家里有一个智能插座是连接饮水机的，还有小米盒子、小米扫地机器人、小米台灯等设备，都可以比较好地联动：\n\n“小爱同学，我要看琅玡榜”\n\n“小爱同学，电视声音大一点”\n\n“小爱同学，扫地”\n\n“小爱同学，扫地机器人还有多少电”\n\n“小爱同学，打开台灯”\n\n“小爱同学，台灯亮度高一点”\n\n“小爱同学，开始烧水”\n\n……\n\n总体来说，小爱同学对声音的响应灵敏度还不错，在房间里也能很自然地交互，对智能家居的打通也做得非常好，用起来真的非常方便。\n\n\u003C!-- more -->\n\n前几天传出来小米估值2000亿美元在香港上市，个人认为，这其中应该至少有800亿美元的估值来自小米智能家居生态。而AI音箱作为智能家居重要的一环，作用不可小觑。\n\n## 一些不爽的地方\n\n前面说了，爽的地方很多评测都说过了，因此不详细说。这里重点说一说不爽的地方。\n\n### 连续自然语言交互\n\n首先会感觉不爽的地方是没有连续自然语言交互的能力。\n\n“小爱同学，放一首歌”\n\n“好的，即将为你播放XXX的XXX”\n\n“下一首”\n\n“......”\n\n“小爱同学，下一首”\n\n“...”（开始播放下一首）\n\n在连续的语音指令后，很容易遗忘“小爱同学”这个称呼。而这个称呼的长度多达4个字，我要说的指令“下一首”本身都只有3个字。多次出现这种情况会让人比较抓狂。\n\n### 音源鉴别\n\n另一个不爽的地方在于小米AI音箱没有鉴别音源的能力。例如我把AI音箱放在电视旁边，我在看电视，然后我叫了一声小爱同学，然后音箱就开始听电视讲台本了，听完一段后装个傻，大家都很愉快。\n\n说它完全没有音源鉴别能力也并不准确。因为我做了一些实验：\n\n- 音箱在播放声音的时候是可以同时监听语言的，比如放歌的时候（不论是蓝牙音源还是音箱自己放的歌）可以被唤醒\n- 音箱在播放声音时，来自音箱自己的声音无法唤醒自己（例如连上蓝牙后，用手机播放“小爱同学”）\n\n因此，我猜测这里应该有用一些类似双麦降噪之类的技术，来区分声音是否是自己发出的。如果不是的话才进行唤醒响应。当然也不排除这只是个巧合……\n\n如果已经有这样一些音源定位的技术的话，我觉得定位唤醒人的音源是非常重要的，谁叫的我，我就只听谁的，这样应该可以解决上面说的电视声音干扰的问题，提升使用体验。\n\n### 语音交互的问题\n\n自古以来，人跟机器的交互都是采用确定性的方式，不管是机械手柄还是实体键盘还是触控屏，机器展示出来的界面是确定的，人的操作也是确定的。一个操作会有一个确定的反馈。这实际上是人适应机器的过程。\n\n但是语音交互打破了这种确定性，变成了机器适应人。那么，一句话说出去，机器会有怎样的反应，这个过程就变得不确定了。\n\n“小爱同学，台灯亮度调高一点”\n\n“小爱同学，台灯色温调高一点”\n\n这两句在人看来都是非常容易理解的，然而，小爱同学只能处理前一句。\n\n表面上来看，这个问题主要在于工程师的适配库不够强大，没有把色温调节这个功能点适配进去。但是深层次来看，这其实就是语音交互自身的问题。没有了确定性，那么对任何一句话的回应都是不敢做期望的。\n\n于是，一开始拿到AI音箱的时候，想什么都通过它来完成，但是在使用时间长了之后，会发生一些心理变化。\n\n例如，让AI音箱调大电视音量，如果漏掉“电视”两个字，就变成了调大AI音箱的音量，然后你需要重新再喊一次。而如果此时电视声音略大，就可能识别失败。于是两三次之后我重新拿回了遥控器调电视音量。\n\n所以语音交互的确是带来了巨大的便利性，但是同时也带来了巨大的不确定性。如果反复碰到这个不确定性带来的不好的结果，我会怀念确定性的控制方式。\n\n再比如，用AI音箱控制台灯，每一次控制完之后都会担心，我的控制是不是真的生效了，然后会跑去台灯那里看一下，控制是否生效了。（虽然这个场景不太真实，但是智能家居多了之后一定会有控制不在身边的电器的需求。）这本质上是一个对智能家居智能控制结果不太任何的体现。回想一下，我用手机控制的时候这种担心会少很多，这是因为手机上可以看到设备的当前状态，而AI音箱并没有直观反馈当前状态的地方。\n\n这些问题其实都是交互设计的研究范围。以UI的交互设计为例，经过几十年的发展，目前UI设计理论可以说非常成熟了。但是语音的交互设计体系才刚开始，如何给用户做合理的反馈，如何管理用户的心理预期可能是接下来AI音箱们需要解决的一个非常重要的课题。\n\n## 安全问题\n\n一个音箱会有安全问题？？嗯。一开始我也没有想到这个问题，可能第一次被问的话，我也应该会回答没有吧。\n\n但是，当这个音箱是小米AI音箱，背后接着那么多智能家居的时候，这个问题就变得严肃了。\n\n举个例子吧。小米生态链最近有一款智能门锁，可以接入米家，支持蓝牙、指纹、密码、钥匙开锁。虽然没有确认，但我觉得可以接入米家的应该也可以接入小米AI音箱吧。\n\n那么，如果我站在家门口，喊一句“小爱同学，请开门”……原来芝麻开门的故事真的不是童话……\n\n同样的，就算你不用门锁，你总有插座、电视、空调、扫地机器人、台灯什么的吧。如果一个陌生人在楼道里就能控制你的家电，这应该可以算是非常严重的安全问题了吧？\n\n因此，小米AI音箱在全面接入智能家居之前，声纹锁应该是个必备功课，否则爆出问题是早晚的事情。\n\n这背后其实是一个控制权的问题。当我拿起一个遥控器，我就有控制权，这个控制权靠遥控器这种实物来保证。当我拿起米家APP，我也有控制权，这个控制权靠手机这种实物（以及手机的安全机制）来保证。而当我放一个音箱在家里，这个控制权要靠什么来保证？\n\n所以，在使用小米AI音箱的这几周中，我跟它接触得相当多，而每次接触都只做一件事情——关闭麦克风。至少这样能在心理上有一种我掌握了控制权的安慰。至于是不是真的掌握，作为一个做技术的人，是不敢相信的。\n\n因此，小米AI音箱要解决的第二个问题，是确保麦克风真的可以被关闭，并且有独立的工作状态指示灯，且这个指示灯跟麦克风必须是硬件级别的关联，不可以通过软件进行修改。（可参考macbook摄像头的指示灯）\n\n最后一个跟安全相关的问题是隐私。事实上在之前有很多获得小米AI音箱F码的机会，但是我一直没有去买，因为我还是不太能接受24小时被一个机器监听。\n\n当我使用其它的数据设备的时候，我可以在使用时开机，不使用时关机或者锁屏或者待机，甚至在使用时我也可以选择暂停、放在桌上或者把摄像头对准窗外。即使是在使用手机语音助手的时候，我也可以选择在我需要的时候再点开它。但是使用小米AI音箱的时候，我并没有这种选择权，不管我在哪个房间，跟谁、朝哪个方向说话，都只能选择由它来监听我。这是由语音的交互形式决定的。\n\n因此，当我确定我在一段时间内不再使用的时候（例如睡觉前），我一定会选择关闭麦克风。否则，所有的主动权全部在机器手上。这是交互形式由人适应机器变成机器适应人所带来的改变，目前我也想不到有什么好的解决方案。\n\n## 小结\n\n鉴于上面想到的诸多问题，AI音箱到底是不是一个真的风口呢？个人依然持有一定的怀疑态度。\n\n交互的进一步完善一边要依赖交互理论的发展，另一边还要重试依赖自然语音交互技术的不断进步。而安全问题仍然会是一个在几年内都需要持续去完善的问题。\n\n再重申一下，小米AI音箱是个非常榜的产品，使用体验很不错，相关的文章网上已经非常多，所以本文重点讲述的是使用过程中感受不太好的方面。\n\n不管怎么说，一种新的可能性正在到来。为小米点赞。\n",{"id":116,"type":7,"slug":117,"title":118,"date":119,"category":11,"tags":120,"body_markdown":123,"permalink":18,"excerpt_src":18,"media_type":18,"media_title":18,"media_author":18,"media_url":18,"rating":18,"layout":18,"pv":19,"admin_only":19,"created_at":20,"updated_at":20,"deleted_at":18,"status":21,"cover":22},122,"why-nobody-cares-tencent-tech","【问答】为啥没人关心腾讯的前端技术栈？","2017-08-02 16:31",[121,122],"腾讯","阿里","\n本文来自知乎问题[为啥没人关心腾讯的前端技术栈？](https:\u002F\u002Fwww.zhihu.com\u002Fquestion\u002F63278539\u002Fanswer\u002F207369772)\n\n业界对阿里前端的关注度的确是比腾讯的要高好多。个人以为主要原因如下：\n\n## 1. 公司宣传策略不同\n\n几年前参加过阿里的校招的宣讲会，令我十分意外的是，一场校招宣讲会，居然让章文嵩博士去做了大篇幅的演讲。（不了解其人的可自行搜索。）整场听下来，会传达一个非常重要的基调，就是“我们的技术非常好，这里非常多的牛人”。后来也有幸去参加过阿里的一些培训活动（类似百淘\u002F百支之类的），活动的主线也是让我们去采访阿里各行各业的牛人（当时叫“牛P”）。\n\n同年，也参加了腾讯的校招宣讲会，腾讯的宣讲会有非常大的篇幅在讲企业文化和公司福利待遇。公司的愿景是什么，文化氛围是什么，薪酬组成如何，奖金如何，班车夜宵如何等等。\n\n两家公司都非常有吸引力，但是策略是完全不同的。校招当然只是一个窗口，但是反映出两家公司对外宣传的一些策略确实是非常不一样的。包括现在，很多人对阿里的印象都是技术氛围好，对腾讯的印象则是文化和福利待遇好。这个印象的不同并不是天然形成的，而是公司有意营造的结果。\n\n\u003C!-- more -->\n\n## 2. 公司文化不同\n\n阿里的文化被戏称“土俗骚”，这让公司保持了相当的活力，甚至是让外人觉得有些出格的活力。同样，在技术上，也有类似的氛围，据我的了解，公司对技术氛围的营造、技术交流等活动还是非常支持的，所以大家能看到阿里在对外技术输出、开源、新技术实践等领域是非常活跃的。这也会导致大家非常关注阿里在技术上的一举一动，甚至很多时候会作为一个非常重要的风向标。\n\n腾讯的文化则偏向于比较正能量的方向，鼓励积极向上的文化。但是这个对技术氛围没有什么积极的影响。腾讯更多的是产品文化，要把产品打磨到极致，技术为产品服务。腾讯的技术人员到晋级T3以上的时候是需要答辩的，而这个答辩的时候海量产品实践、为产品做了多少事情其实是一个非常重要的考量指标。这直接导致“进取的员工”会花更多精力在产品实践上。（这里进取的员工是指看清了公司职业道路，希望积极表现的员工，并不是说只研究技术的员工不进取，只是这个在公司职业道路上的帮助相对没那么大。）而对外输出、开源等事情相对来说就没有那么重要了，公司虽然原则上不反对，但是并没有什么实际上的支持。所以我们看到腾讯的技术输出是非常少的，公司层面也不会宣传自己的技术有多好，开源氛围有多好等。\n\n这个差别导致大家的关注点非常不一样。我们知道阿里首页用了Node，知道它们在App中用了weex。但是你知道微信用了什么，手机QQ用了什么，腾讯网用的什么么？\n\n## 技术实力究竟有没有差距\n\n我觉得这个问题得两说。\n\n如果说技术实力是指对某一些技术领域的掌握，那我觉得可能还真的是有差距的，比如在Node这一块，阿里有好几位参与对Node核心代码贡献的，也有朴灵这样的大牛，自己做出了alinode这样的产品，而腾讯这一块，目前并没有太大动作。\n\n但是如果以技术支持产品的角度来说，其实没有本质差别，Node掌握没那么好是吧，行，我们用C++\u002FPHP一样支撑起来。无非是并发、延时、日志、容灾、安全这些事情嘛。\n\n同样，从前端角度来说，可以说腾讯没有出过组件库，没有什么成功的开源产品，但是面对前端这些常见的技术场景，也确实并没有什么技术难度，大家都能做得很好。\n\n## 未来会有改变吗\n\n我觉得会的。\n\n阿里某一些开源项目，或者技术栈的选用，也并非都是那么好的结果。只能说态度很积极，结果怎么样，还需要时间考验。事实上不管是过去还是现在，吐槽阿里技术的也并不少。（前端相关的比如 kissy \u002F 阿拉雷 \u002F seajs \u002F weex ，并不是没有意义，而是不够好。）随着开源这个东西的“神性”下降，大家已经可以更理性地看待开源的意义，也明白并不一定高举高打的都是好东西。\n\n我不确定阿里是否会有关于开源项目持续维护更好一些的机制，但不管怎样，我觉得这里是值得思考的。\n\n腾讯也并不一定会对技术分享、开源一直封闭。事实上微信最近已经开源了好一些作品，反响也还不错。另外云计算的战争开始打响，让大家都意识到，这个领域是一个一定要对开源、对社区友好的领域。所以我们看到阿里在做技术社区，腾讯也在做。\n\n综上：阿里新技术实践相对比较多，且因为种种原因，让业界都知晓，所以关注自然多。腾讯的新技术实践相对少，或者实践完没有让业界知道，关注自然也就少了。\n",41]