第1章 善用设计原型,探索项目创意
本章背景:你是能力很强的程序员,并且正在发挥技术特长,运用速成原型法帮助人们探索新的产品创意。
尽早生成可工作的软件,可以令产品设计变成交互式协作过程。高效的反馈环有利于快速识别潜在的不良设计,并对此提出解决方案,以免日后在更关键的阶段浪费大量时间和精力。
1.2 利用线框图表达功能需求
不要花费过长时间去讨论子系统(书中实际例子为推荐系统)的完美实现方式,而应该先集中精力寻找“最简单可行的方法”。
1.3 编程之初立即搭建测试系统
第一次发布的真正目的是创建一个可用的系统,以便提高后续的变更速度,并由此开始探索项目创意的过程。
1.4 全面探讨不足,改善追求实效
完全不犯错误是不可能的,但最关键的是你对这些问题作何反应。
如果拖得越久,问题是不是越难解决?如果答案是否定的,那么这个问题是可以暂缓的。让出时间给更核心的问题。
每当发现自己的软件有瑕疵时,你可能想要停下手中的工作,立即去修复。但是在项目的探索阶段,你得去平衡软件缺陷带来的损失和修复这一缺陷的时间成本。
1.5 早问多问,验证设想
尽早探知客户头脑中的想法,早问总比晚问好。所谓“学之乃知,不问不识”,多问问总不会有坏处!
力求缩小自己的工作范围。
谨记原型并非生产系统。
1.8 巧妙设计特性,轻松收集反馈
将技术思想可视化,方便与客户进行探讨并收集反馈。
在原型阶段结束之前,应该至少会有一个始料未及的重大问题浮出水面。
本章小结
- 多向项目参与者提一些能够发掘其目标的问题。这样一来,你既可以验证自己的想法,又可以更好地了解他人对问题的看法。
- 绘制线框图(草图)可以清晰地和他人探讨应用的结构,不会因为被样式细节绊住而停滞不前。
- 一定要在一开始写代码的时候就搭建一个测试系统,让大家都能与其交互。测试系统不需要完善到满足上线要求,只要适合收集有用的反馈即可。
- 在项目早期,集中精力解决有风险或未知的问题。建立原型是为了探索问题空间,而不是为了做出完整的产品。
第2章 观察增量变更,发掘隐藏依赖
本章背景:工作变得复杂起来。你需要逐步扩展现有系统,同时需要为很多活跃客户提供支持。你发现理想丰满而现实骨感:一方面,你想按照自认为正确的想法进行工作;另一方面,客户不断给你施压,要求你尽快交付新的特性。
你将了解到,在逐步扩展代码库的过程中,哪些出乎意料的问题会从天而降。
不存在所谓的“独立特性”
2.2 两特性同屏必相互依赖
这些赶出来的工作会在以后出问题时反过头来咬你一口。
对数据库模式所做的任何修改都应该考虑到数据的一致性。不管新特性在代码层面上多么独立,在数据层面上仍有可能存在隐藏的依赖关系。这就意味着,为了支持某一特性而在代码库的某一部分进行的模式升级,可能会使一些看起来毫无关系的其他特性崩溃。(书中的例子是,为数据库新增了一列,但对于老数据,这一列的值是null,由此导致了异常。)
避免不必要的实时数据同步。
本章小结
- 不要因为某个变更没有明显改变现有特性,就认为它会向后兼容或绝对安全。相反,应该对隐藏的依赖关系随时保持警惕,即使进行的是最简单的更新操作。
- 注意除代码库之外的大量共享资源:存储机制、处理能力、数据库、外部服务、库、用户界面,等等。这些工具形成了一张“隐藏依赖网”,会给看起来毫无关联的应用特性带来副作用或引起故障。
- 利用限制和验证的方式,在最大程度上防止局部故障对整个系统造成影响。但还要确保系统拥有良好的监控机制,以保证快速知晓和处理突如其来的系统故障。
- 在复用现有的工具和资源时,要尤其注意使用环境的变化。任何对使用范围、性能标准或隐私安全级别的改变,如果不经过仔细考虑,都可能引起非常危险的问题。
第3章 准确识别痛点,高效集成服务
本章背景:你对草率决策的代价,尤其是在整合外部代码的过程中仓促决策所带来的损失,有了更深的理解。你从过去的错误中学到很多,并开始关注业务、客户服务及技术工作三者之间的复杂关系。
如果不将服务集成考虑清楚,会对决策产生不良影响。
3.1 面对小众需求,切记未雨绸缪
应该多做一些调查,看看有没有其他人也在以你的这种方式使用该服务。也许很难找到这样的人,这本身就该给你敲响警钟,让你停下来思考一下为什么没有人这样用。
3.2 谨记外部服务并不可靠
“五个为什么”游戏:一般用于发掘问题的根本原因。做法是通过反复问“为什么”来提示问题的大背景。由于大部分问题的根本原因不止一个,因此为了从不同角度发掘原因,可以根据需要,不断重复询问(追问)。
过期的第三方库仍然可用(只要代码库及其基础设施中没有引入不兼容的变更)。但Web服务(接口)是一种完全不同的依赖。
3.3 服务一旦有变,查找过期的模拟对象
现场测试非常重要。
如果再遇到服务依赖变更,我们除了要进行代码复查,查找过期的模拟对象,一定还要定期运行小规模的自动化测试来审查服务本身。
3.4 遭遇烂代码,维护必头疼
在开放的互联网环境中,你需要担心的不只是自己集成的服务。有时,会有人不请自来地把你和他们自己集成到一起。(比如爬虫,攻击等等)
3.5 不存在纯粹的内部问题
为发现的所有问题都添加回归测试,无论问题有多小。
务必检查测试配置中的模拟对象,以免测试结果让你错误地认为模拟的API客户端仍能正常工作。
异常报告系统最好能把相似问题合成一个报告。
本章小结
- 当你的应用依赖于你不甚了解的外部服务时,一定要加倍小心。如果找不到和你的用法相似的成功例子,就要提高警惕:往好的方面说,可能没有人用过该服务解决与你的需求类似的问题;往坏的方面说,该服务可能根本就不适合你。
- 谨记库和服务之间最关键的不同点:只有在代码库或基础设施发生改变时,库才会引起显著变更;而外部服务随时都可能让系统行为发生改变,甚至导致系统崩溃。
- 只要服务依赖发生变更,就要在测试中密切关注是否有模拟对象过期。为了防止被测试结果误导,一定要确保针对你所依赖的真实服务运行一些测试。
- 认真对待每次代码复查,把它当作对所依赖服务的一次小规模审查,比如评估测试策略、考虑如何处理故障,或者思考如何防止错用资源。
第4章 设计严密方案,逐步解决问题
背景:你已经成为了经验相当丰富的开发人员,并且有能力教别人如何看待编程和解决问题。你现在已经开始指导新入行的朋友了。
4.3 核实输入数据,随后进行处理
现实中的原始数据一般都是一团乱麻,所以在使用样本数据前对它进行处理,这再自然不过了。
欲解复杂问题,先知简单情况。
本章小结
- 描述问题的原始资料一般都是零散的语句、示例和参考材料。为了理解这些资料,你需要记笔记,排除噪声信息,只留下最关键的细节。
- 每个问题的背后都有一堆简单的子问题,你早已知道如何解决它们。要将问题不断拆分,直到你能辩认出构成它的子问题为止。
- 难题由很多活动部分组成。先观察各个部分如何联系在一起,而不要管具体的实现细节。在写代码之前,先用纸笔解决部分问题。
- 在无效数据集上运用有效规则可能会得到难以调试的混乱结果。不要假设输入数据集的格式正确。在处理任何数据集之前,都要预先检查,以避免出现“垃圾进、垃圾出”的情况。
第6章 认清现实瑕疵,改善数据建模
背景:你在通往大师的道路上继续前行。你可以指出公司遗留系统的弱点,并设计合适的组件进行替换,既使商业效果得到优化,又使工作流程更加友好。
6.1 分清概念建模和物理建模
在数据源混乱的系统中,一般最好保留一定的灵活性,不要在模型的物理层强加太多结构。
6.2 明确设计模型,追踪数据变化
事件溯源模式(更多参考)将独立事件建模为不变的数据。这些数据代表不变的事实。通过遍历事件序列并计算结果,可以看到系统当前的状态。
康威定律:设计系统的组织,其产生的设计和架构等价于组织间的沟通结构。如果你的系统设计或者架构不支持,那么你就无法成功建立一个有效的组织;作为系统架构师,一定要深入领会康威定律,设计系统架构之前,先看清组织结构,也要看组织文化(民主合作式,集权式,丛林法则式,人才密度),再根据情况调整系统架构或者组织架构。(更多架构和康威定律的论述参看这里)
本章小结
- 保留数据的原始格式,不要试图立即将其转换为与概念一一对应的结构。你当然可以把原始数据处理成任何格式,但从复杂模型中提取与原始数据相同的信息,会带来不必要的麻烦。
- 在开发数据模型时,仔细考虑数据将被如何表示、查询和修改。在实践中,很少有项目只需要针对数据进行创建、读取、更新和删除等简单操作,因此一定要量体裁衣。
- 要把预览、备注、批准、审查和撤销事务性数据变更等操作设计得简单易行。要做到这一点,需要另外写代码,而不是依赖于预建的库。另外,在数据模型中采用事件溯源模式可以简化工作。(书中的实例是员工打卡系统)
- 数据管理工作流的设计要尊重和支持软件使用者的组织文化。若在设计时不考虑康威定律,系统很可能会被成千上万种使用方法压得不堪重负,最终崩溃。
第7章 逐渐改善流程,合理安排时间
背景:你对整个软件行业都已经足够熟悉。无论组织的哪个层级出现问题,你都能发现并修复。你的核心竞争力仍然是软件开发,但足够丰富的经验使你能很好地与各个层级的人员进行交流。
7.3 注意权衡工作的经济效益
帕累托法则:很多情况下,20%的投入就会有80%的产出。
7.4 限制积压工作,力求减少浪费
突破工作过程中的一个瓶颈,很自然地会让人看到另一个瓶颈。这是进步的标志。
未上线的代码不是资产,而是存货。而且,这些存货还容易腐坏,并具有持有成本。
若想跟上进度,要么放慢发布节奏,并且减少开发工作,要么加速反馈环。我认为将三者结合可以达到最佳效果。
7.5 力求整体大于部分之和
“玫瑰、花蕾和荆棘”的形式总结:“玫瑰”代表发生的好事,“花蕾”代表大有希望的事,而“荆棘”代表遇到的痛点。
海盗指标(更多参考):(AARRR metrics, AARRR分别代表Acquisition、Activation、Retention、Revenue和Referral,即获取、激活、留存、收入和推荐),这些指标对任何公司都至关重要。
要足够了解周围每个人都在做什么,以便了解自己的工作是否符合大局。比如,每周让一位开发人员抽出一天时间解决客户支持团队认为值得处理的“小问题”。每周轮换,让所有开发人员都有机会处理客户的问题,从中获得经验。
本章小结
- 在处理系统级故障时,要根据需要停用或降级一些特性,使软件尽快回到可用状态。一旦压力得以缓解,便可以开始认真修复发生故障的部分。
- 寻找过度耗费时间的部分,用合理的安排加以限制,这样就可以省下时间进行其他工作。做决策不能只靠直觉,要使用“毛估”计算法,将经济因素也考虑进去。
- 谨记未上线的代码不是资产,而是存货。存货容易腐坏,并且具有持有成本。要帮助项目中的每个工作人员理解这一点。做法是让他们集中精力在给定的时间段内交付有价值的工作,而不是让团队中的每个人都为了忙碌而忙碌。
- 与分工不同的人合作时,试着用对方能理解的方式进行交流。从旁观者的角度看待问题,并思考:“这件事与正在和我交谈的人有关吗?这件事对整个项目有何影响?”
本章的案例问题及解决方式值得反复研读。
第8章 认清行业未来,再议软件开发
在不久的将来,“程序员是用技术解决人类社会常见问题的人”这一观点可能成为行业标准。
程序设计中最有趣的一直是解决问题、沟通等“以人为本”的方面;代码只是我能找到的解决问题最有力的工具而已。
站在足够高的层次与计算机交流,只需要关注如何解决问题,而不是纠结于代码编写中的细枝末节。
如果你想保持头脑清醒、不忘记问题背景,就需要一遍又一遍地问:“等等,我想解决什么问题来着?”