专业素养
关心你的技艺 思考你的作为
专业主义
专业主义意味着担当责任
- 提供各种选择 不要找蹩脚的借口
说明做不到之前 给出所能给出的最好方案以尝试挽回
损害
软件出现bug 就是在损坏软件功能
编写测试 让它自动化跑出起来 测试越多 你对自己的代码越有信心
牺牲结构来修改软件 后果是得不偿失 软件的特点就在于软 如果修改难以进行 就代表设计出现了问题 留着这些破窗户只会导致破窗越来越大 不要容忍破窗户
当软件的熵越来越大时 软件也就越来越无序了 称之为软件腐烂
使用无情重构的方式来避免代码慢慢腐烂 但重构的前提是完备的测试 测试是软件质量的保障
留意环境 警惕那些软件腐败的小细节
职业道德
专业人士应舍得投入时间不断提升自己 变得更加专业
似乎软件开发这个领域出现知识大爆炸 感慨学不过来 但目前这些所谓的流行技术 绝大部分都是来得快去得快 那些来之不易的理念 绝大部分在今天仍然十分有价值 在学习时 应该把重点放在这边
- 设计模式 设计原则
- 开发方法
- 软件工程实践
- 不会过时的工具
- ...
持续学习才不至于落伍 不写代码的架构师必然遭殃
这个行业的知识更新迭代的速度十分快, 你需要像金融投资那样经营你的知识资产:
- 定期投资让你不至于你的知识一成不变 落伍
- 多元化 知道的不同的事情越多 越有价值
- 管理风险 新兴知识的投资高回报高风险 不要把所有的技术鸡蛋放在同一个篮子
- 需要时 对投资进行重新评估与选择
批判分析读到和听到的
练习能让你的技艺保持熟悉 什么样的练习 刷题?
与他人合作能从彼此身上学到很多东西
费曼学习法
技术人员应付出相当的努力来认识业务领域,既然拿了公司的钱,就要熟悉公司的内部技术达到一个说得过去的水平
站在雇主角度开发软件 使质量称为需求问题, 有时候需要在尽早交付与完美软件之前权衡 编程就像绘画 需要知道何时止步 一昧求精只会导致损毁掉你的程序
不必故作谦逊 摔了跟头大不了一笑了之
说不
面对艰难决定 直接面对是最好的办法
对于非技术人员 ”为什么“只是个技术细节 对它们俩说 并不重要
合适的时机说不 意味着团队精神 意味着对团队负责
客户所要的任何一项功能 一旦写起来 总是远比它刚开始时说的要复杂许多
说是
模糊不清的词是缺乏承诺的征兆
使用具体时间来承诺 那么就要为承诺负起全部责任
- 只能承诺自己能完全掌握的事情
- 即使无法完成目标 也该努力前进 离目标更近
- 如果无法兑现承诺 应及时发布预警
如何说是:
- 试试看意味着不确定感 对于明确有着不确定的任务 应该表达出不确定感
- 同时仍需要坚守原则 作为一个专业人士
主观能动性
- 完成需求并不够,还需要深层次满足用户的需求
交付思维:理解用户需求背后真正想要的东西,然后努力向这个目标发展
注意时间:精益求精的同时在规定的时间内完成优先级更高
责任边界
- 对安排的工作负责:除了保质保量完成之外,还要提早暴露风险,抛给上一层的人做决定
- 对工作时间负责:解决问题时在线、准时参加会议
编码
你不可能写出完美的软件
防御式编程不仅仅防御别人的代码 还要防御自己
传统的瀑布模型一旦进入编码阶段 就是机械地将设计转为可执行语句 这种没有任何创造性的编码或许是造成软件结构糟糕 低效的原因
出错感知能力能帮助你更快速地从错误中学习
准备:
- 编码前必须要理解解决的是什么问题以及该如何解决
- 确保代码能解决客户的问题 而非完成需求
- 新的代码应能完美适应当前系统
- 写下的代码应该具有可读性
疲劳与心烦意乱下的产出 最终只能回头返工
一味追求速度可能会导致思考角度边狭隘 从而做出一些以后不得不推到再来的决策
编程时被中断再回来会导致上下文成本切换很高:
- 结对编程的伙伴可以帮助维护上下文
- TDD失败的测试也可以快速让你回到状态
心情/精神等因素会阻塞你的创造性输出 相反 一些创造性输入可以激励你产出
调试时间的多少与专业程度成反比 向着零调试时间前进
难以解决的困难不妨放一放 等待灵感的到来
冲刺可以解决进度延迟问题 但不一定会成功 需要准备后备预案
只有通过验收测试 某个特性才能称之为完成
帮助他人 接收他人的帮助
合约式编程
DBC:
- 前条件:调用程序前 必须为真的条件
- 后条件:程序保证会做的事
- 类不变项:确保从调用者的角度看 总是为真的条件
实现方式:
- 断言 但是无法继承
- 部分语言内置支持 但是大部分语言不支持 通过使用预处理器来实现它
早崩溃:
通过检查前置条件 早一点暴露错误 调试就会容易许多
尽早崩溃比造成破坏是更好的选择 Java中的运行时异常采用了这一哲学
不变项:
- 循环不变项:循环的边界很容易出错 通过定义一个不变项来证明结果有效
- 语义不变项:定义一个合约来表达不可违反的需求
动态合约与代理:合约可以发生变化
断言式编程
如果它不可能发生 用断言确保它不会发生
不要关掉断言 这世界很危险 程序很容易出错
异常的使用
异常不应该是程序正常流程的一部分 而是留给意外事件
不支持异常的编程语言只能使用错误处理器 c语言可以通过使用goto的方式来实现全局异常处理
资源的使用
- 有始有终 分配资源的程序应该负责回收资源
资源的分配:
- 以资源分配的反序释放资源 这样就不会造成资源被遗弃
- 分配同一组资源的时候 总是以相同的顺序进行分配 这可以降低死锁发生的可能性
资源的释放:
- 递归回收 顶层对象一旦被释放 就递归地释放子资源
- 顶层回收 就直接遗弃所有子资源
- 如果顶层含有子资源 在所有子资源释放前 拒绝释放顶层
靠巧合编程
编程时 依赖着许多假定的条件 有时候这些条件也许存在 从而你的程序能偶尔可以工作正确 但更多地 它会在你未来的某一天崩掉
算法速率
使用大O表示法估算你的算法
重构
早重构 常重构 重构必无情
何时重构:
- 重复
- 非正交设计
- 过时的知识
- 为了性能
易于测试的代码
为测试而设计 使用合约式编程来清晰测试
谨慎代码生成器的使用
不要使用你不理解的代码代码生成器 这些代码未来可能会跟你你编写的代码柔和在一起 如果不理解它们 未来就是一个定时炸弹
注重实效
重复
系统中的每一项知识都必须具有单一 无歧义 权威的表示
不要重复你自己
- 强加的重复
好像让开发者没得选择 必须写重复的文档 做重复的编码 如根据规约写出代码 但只需要动用一点小才智 就能让这个过程自动化
把低级的知识放在代码中 把注释留给高级的知识
- 无意的重复
这种重复一般是设计的错误 需要从根源解决问题
- 无耐性的重复
就跟提到的赶工期取消单元测试一样 编码一时爽 维护火葬场
- 开发者的重复
这个问题似乎在2020年的今天已经不存在 开源社区的繁荣促使开发者代码复用变得十分容易
正交
消除无关事务之间的影响
正交在几何中表示的是如果两条线称直角 则就称之为正交
对应到计算机世界 就是解耦 一个模块的变化不会影响到另外一个模块
正交的好处:
- 提高生产率
- 小模块的编写总比大模块容易
- 促进复用
- 降低风险
- 有问题的代码会被隔离
- 改动影响的范围有限
- 测试更容易
在团队中:正交性差的团队成员职责边界不清晰
在设计中:分层的方法是设计正交系统的强大途径
引入第三方库时 是否需要对已有代码进行改动 如果是 那么就不是正交的
每次编写编码 都有系统降低正交性的风险 几个原则来维持正交性:
- 保持代码解耦 避免不必要的数据暴露 使用OOP来封装
- 避免全局变量
- 使用设计模式
正交也适用于文档 正交文档表现形式与内容分离 比如markdown
可撤销
如果某个想法是唯一的想法 那就太危险了
总需要保持代码的灵活性来避免变动带来的返工
不存在最终决策
曳光弹
小步快跑 快速迭代 帮助用户明确需求
小段代码的惯性很小 改变起来很容易
原型与便签
应制作原型的事物:
- 架构
- 新功能
- 外部源
- 性能问题研究
- UI
适当使用原型 节约时间金钱
领域语言
计算机语言会影响思考问题的方式
靠近问题领域进行编程 站在更高的抽象层面 忽略琐碎的细节
基本工具
纯文本
使用纯文本保存知识
二进制数据的问题在于没有第三方对其解析 这些数据毫无意义
文本的威力:
- 保证不过时 人能阅读的数据 以及自描述的数据 活的更久
- 杠杆作用 计算机世界的许多工具对文本的支持都不错 unix下的小工具 vcs等
- 易于测试 文本测试数据更容易修改 并且无需特殊工具
纯文本在异构系统下十分好用
shell
shell是不是真的比GUI好 好在哪里?
GUI最要命的一点 那就是使用GUI完成操作受限于GUI设计者 你所做的 都是在设计者给你的条条框框内
利用命令shell的力量 你可以使用组合参数 管道等方式得到一些十分强大的命令
编辑器
用好一种编辑器 这个编辑器应该能横跨所有平台(GUI 与命令行) 我能想到的就只有vim 但是vscode也符合需求 它的确很强大
源码控制
总是使用源码控制 不仅仅是代码 一切一切都可以存入源码控制系统 他给你了你反悔的能力
调试
要修正问题 而非发出指责
不要恐慌 你最容易欺骗的人是你自己
从何开始:
- 收集更多数据 以清楚bug
策略:
- 把你的数据可视化
- 跟踪程序 跟踪数据
- 橡皮鸭调试法
- 你的程序出错的可能性比外部程序大
- 不要假定 要证明 出现了令你惊讶的bug就代表你之前的假设是错的
文本操纵语言
学习一门文本操纵语言可以有效提升效率
代码生成器
编写能生成代码的代码
被动代码生成器:生成结果 结果可以独立使用
主动代码生成器:需要时进行生成结果 结果用完就扔
代码生成器生成的不仅仅可以是代码
练习
用自己的时间练习 保持自己的技能不落伍是自己的责任
验收测试
需求
过早精细化带来的问题:
- 每次向业务放展示一项功能 他们就获得比之前更多的信息 这些信息又会影响他们的看法 提出新的观点
- 需求一定会变化 过于精确的评估无效
- 但是拒绝过早精细化又会带来模糊性
验收测试
其目的确定需求已经完成
何为已经完成:代码都写完了 测试都通过了 QA和需求方都认可
通过沟通确保大家都明白要做的什么
验收测试应当自动化进行 手工测试的成本太高
验收测试的进行越晚越好 需求一定会变化 理想情况下 应该由业务方以及QA来编写这些测试
协商并改进测试时专业开发人员的职责
单元测试与验收测试的区别在单元测试时白盒 验收是黑盒
对于GUI测试 进行时必须使用GUI背后文档的抽象元素 但是GUI测试还是应尽可能减少 设计时做到GUI与业务逻辑的解耦 GUI测试时不稳定的
使用持续集成确保新增的代码不会导致测试失败 否则修复失败是第一重要任务
测试策略
开发人员与QA携手保障系统质量
自动化测试金字塔:
- 单元测试作为持续集成的一部分来运行
- 组件测试需要使用合适的模拟 输入数据 收集输出 验证是否符合预期
- 集成测试主要测试组件装配在一起是否协调
- 系统测试测试系统是否已正确组装完毕 各个组件之间是否能正确交互
- 最后使用人工探索式测试尽可能找出多的古怪之处
时间管理
离开没必要的会议
按照真实的紧急程度来执行任务
进入死胡同或者泥潭时 你可以回头修正设计 也可以继续向死路走下去 但走回头路是最简单的办法
预估
估算,以避免发生意外
根据实际情况来调整你的估算
使用的单位会对结果的解读造成影响
承诺是关乎确定性的 预估是一种猜测
尽可能说明预估的概率分布
三元分析法:
- 乐观预估
- 标称预估
- 悲观预估
压力
保持冷静的最好方式 便是规避会导致压力的处境
尽力为其他人的承诺找到解决方法 但并非要为别人的承诺付出代价
保持整洁 不能因压力而破坏原则 快而脏是矛盾的
遵循那些仍会在危机时刻遵循的原则 这些原则是避免陷入危机的最好途径
避免产生孤注一掷的想法 仓促鲁莽只会把你代入更深的深渊
协作
程序员的工作职责就是要让业务免于陷入困顿
代码共有比代码私有带来的好处要更加多
结对编程不仅促进知识传播 同时也是复查代码的一种手段
说什么与怎么说同样重要
做变化的催化剂
解耦
迪米特法则
该法则试图使各个模块的耦合降至最低
工程需要平衡各种证明因素和负面因素 带来解耦的好处 就会带来其他代价
元程序设计
元数据是描述数据的数据
那么元程序就是元数据驱动的引用
将抽象放进代码 细节放进元数据
好处:
- 耦合更低
- 推迟细节决定
- 无需重新编译
- 更接近于领域模型 可以通过调整元数据实现不同的应用
可配置的应用 配置度越高 就代表程序越抽象 越抽象的程序越容易复用
时间耦合
相较于并发程序 顺序程序更符合人的逻辑
如果容许并发 就代表与时间的耦合度降低了 并再设计上予以支持 虽然不符合人的正常思维 但可以获得更强的灵活性
分析工作流 以改善并发性
用定义良好的 接口一致的服务来进行设计
为并发而设计能促使你设计更加简洁的接口
一旦具有了并发的要素 部署起来更加容易 因为不会有强依赖的情况产生 所谓强依赖就是部署a之前必须部署b
只是视图
- 发布订阅模式
MVC中的视图与模型分离
这条原则不仅适用于GUI 而是一条通用的编码准则 用来降低耦合
项目
需求
不要收集需求 而是挖掘它 深刻明白客户所提的需求可能是表象 要通过表象洞察其本质
建立需求文档:不管是形式化还是非形式化 不管是用例还是普通流程图
避免过度:文档比代码的好处就是可以模糊不清 文档不必过于具体 保持适当抽象 抽象比细节获得更加长久
追踪需求:通过建立一系列跟踪计划来避免特性膨胀
维护一张词汇表:建立业务领域与技术领域之间的映射
这些需求文档的分发可以借助于VCS以及Web 又厚又大的打印文档不仅费时费力而且容易过时
解决问题
一个难题的最终解决方案是一个刚开始看起来不太适用的方案 尝试思考被排除的方案
准备好了吗
对于反复出现的焦虑 需要重视它 准备好再开始
如使用原型来验证想法是否可行
规范陷阱
不应该将需求分析以及编码分裂成不同的过程 这些过程相辅相成 也不应该将规约设计到编码时毫无发挥技巧的余地
圆圈与箭头
不要做形式化方法的奴隶 编码是一个创造性过程 昂贵的工具不一定能制作出更好的设计
团队
不要留破窗户 破窗户最终只会越来越破
不要做温水里的青蛙 警惕那些使项目失败的小细节
交流失败的团队注定会失败
DRY:团队成员的工作重复不仅带来浪费 而且维护还可能是噩梦
围绕功能而非工作职务进行组织人员 就像围绕业务而非技术
抵抗不断画下去的诱惑
自动化
手工流程是不可靠的
- 构建自动化
- 自动化管理
- 网站生成
- 批准流程
无情的测试
早测试 常测试 测试必无情