代码的艺术
概述
- 章淼《代码的艺术》分享会后笔记、感悟、理解
- 微信公众号:章老师说
前言
-
动机(Motivation)
-
写代码,学校和公司有很大的不同
- 学校的代码不符合公司规范
- 教科书上的函数方法命令命名很随意
- 如:myFunction(),这个方法做了什么,通过方法名称无从知晓
- 公司的代码规范是共用
- 学校的代码不符合公司规范
-
码农35岁转管理的误解
- 很多软件工程师没有做长线的职业规划
- Go语言的开发者半百之后才创建这种语言
-
互联网公司应该加班加点
- 不,国外的公司不加班,但依然高效
- 不是加班就等于高效
-
明确修炼的方向
- 很多人工作五六年后无方向,不知道如何成长
- 不是没有时间学习,更多的是不知道学习什么
-
我们是软件工程师,不是码农;我们不是在编码,而是在改变世界
- Software Engineer
- RD :Research and Development
第一节 概念
- 代码的艺术
- 何为艺术
- 编码是非常具有创造性的工作
- 代码是人类智慧的结晶
- 培养方向
- 编码
- 项目管理能力
- 设计能力
- 技术
- 编码能力、数据结构、算法
- 数据结构、操作系统、分布式系统
- 产品
- 对业务的理解,交互设计,产品数据统计
- 项目管理
- 80%的人不会写代码
- 90%不会写文档
- 设计文档没有推理过程
- 95%不会做项目管理
- 研究和创新
- 研发工程师
- RD : Research and Development
- 但大多数开发都缺少 Research 研究,缺少学习的时间,没有学习的动力
举例:
项目研发过程中,产品开需求评审会
1.开发开始设计表,讨论各种技术实现的难度或细节
2.开发没有详细讨论每个逻辑,没有逐个分析每个业务逻辑是否走得通
3.开发过程中发现原本以为很简单的逻辑,突然走不通了,此时再去想解决方案,可能导致之前的代码重新开发
改进:
1.项目需求评审过程中不要以技术实现不了或目前系统不支持来干预或打断产品的设计思路,而是从一个用户的角度出发,考虑产品设计是否合理
2.系统设计文档,包括逻辑流程图、系统架构图、表结构设计文档
-
艺术的解读
- 艺术,解读的角度
- Coding is not so easy
-
编码过程
- 从无序变为有序
- 从现实世界的问题转化为数字世界的模型
- 一个认识的过程,从未知变为已知
-
编码能力
- 把握问题的能力
- 建立模型的能力
- 沟通协作的能力
- 编码执行的能力
-
编码需要建立品味
- 对自己写的很烂的代码无感,自己觉得很不错
第二节 实践
-
知道了概念,但如何去做
- 实践
-
一流代码的特性
- 正确:Solid and Robust
- 高效:Fash
- 简洁:Maintainable and simple
- 简短 Small
- 共享 Re-usable
- 可复用
- 可测试 Testable
- 可移植 Portable
- 可监控 Monitorable
- 可运维 Operational
- 可扩展 Scalable & Extensible
-
总结归纳
- 正确和性能
- 可读和可维护
- 共享和重用
- 运维和运营
-
不好代码的表现
- 方法名称见名不知意
- do()
- 变量名称
- a,b,c
- 没有注释
- 写函数时不是单一功能
- single purpose
- 单一功能,接口可重用
- 不好的格式,所有代码写在一行
- 不能测试
- 方法名称见名不知意
-
代码不只是写出来的,编码的时间只占整个项目流程的10%,更多的时间应该放在需求讨论及需求分析
-
好的代码是多个环节工作的结果
-
编码前
- 需求分析,系统设计
-
编码中
- 编写代码,单元测试用例
-
编码后
- 继承测试,上线,持续运营/迭代改造
-
一个好的系统/产品是以上过程持续循环的结果
-
-
需求分析 & 系统设计
- 被忽视的环节
- 有太多的人错误的认为,写代码才是最重要的事情
- 有很多在没有弄清目的、目标的时候已经开始做了
- 写代码时是黑盒状态,没有设计文档,其他人不知道你在写什么
- 有太多的项目,在没有搞清楚目标之前,就已经开始编码了
- 有太多的项目,在代码基本编写完成后,才发现设计思路是有问题的
-
软件开发规律和人的直觉是相反的
- 在前期更多的投入,收益往往最大
- 很多人觉得不写代码不踏实,不写代码感觉一天没有成就,所以一接到需求就赶紧写代码
- 软件开发的规律
- 如果没有完整的设计文档,在开发过程中往往需要改代码
- 改代码的成本是很高的,代码中相互耦合的程度很高
- 调整主要流程,往往会影响其他次要功能
- 修改代码的时候往往需要伴随着功能调试
-
需求分析与系统设计的差别
- 需求分析:一个系统软件黑盒的行为外在的行为
- 系统设计:设计系统白盒的行为,内在怎么做?为何这么做?
-
需求分析
- 问题:怎么用简介的语言描述出一个系统的功能
- 每个系统都有自己的定位
- 怎么描述GFS的需求
- 分布式文件系统
- 文件数量:文件大小的分部,总的存储容量
- 读写能力
- 容错的能力(一致性方面的定位,强一致,弱一致)
- 对外的接口
- 怎么描述GFS的需求
- 这些方面都影响系统应该如何设计
- 调研需求、规模
- 由需求决定技术选型
-
需求分析的误区
- 需求分析是产品经理的事情,与RD无关?
- 不合理
- 即使是用户产品的项目,RD也需要参与需求评审
- 例如:购物车,产品与RD写出来的思路不同
- 但大多数情况下的需求讨论会,讨论的都是大量代码的实现细节
- 或者忽略需求分析,直接写系统设计
-
危害
- 阻碍需求分析的聚焦,讨论设计思想,而不是具体实现方案
- 实现决定需求
-
正确的做法
- 从用户角度,确认需求
- 根据ROI确认开发优先级,投入产出比
-
- 需求分析的重要性
- 导弹 VS 炸弹 ,导弹价值更高,导弹的价值何在
- 导航模块
- 需求分析的地位:导航
- 开发过程中投入了大量的时间精力在错误的方向
- 导弹 VS 炸弹 ,导弹价值更高,导弹的价值何在
-
系统设计
-
系统设计写什么
- 不知道该写什么
- 不知道该想什么
- 定义结构、模块、接口
-
系统设计的主要内容
- 系统架构、模块涉及、接口定义、数据定义
- 关键算法、设计思路
-
设计文档的分类
- 总体设计文档
- 子系统设计文档
- 接口定义文档
- 数据库表设计文档
-
文档管理
- 哪些是最新的
- 哪些是无用的
- 如何分类
-
系统架构
- 一个系统架构是概念模型,定义结构、行为、系统更多的师徒行为
-
架构描述的几个要素
- 静:系统如何组成,功能再这些组成部分之间如何划分
- 动:各自系统如何联动,如何完成任务/流程
- 细:从不同的角度,更详细刻画出系统的全貌
-
系统框架在不同层次的体现
- 总体设计:大系统的框架
- 模块涉及:子系统的架构
-系统设计的方法 - 每个组件(子系统/模块)的功能应该足够专注和单一功能的单一是复用和扩展的基础
- 子系统和模块之间的关系应该是简单而清晰的
- 软件中最复杂的是耦合
- 避免使用全局变量
- 两个系统或多线程之间的耦合
- 避免使用全局变量
-
系统设计的约束
- 资源的限制:计算、存储、IO/网络
- 了解知道自己的设计瓶颈在何处,预期发生的问题
-
需求是系统设计决策的来源
- 在设计中,经常需要做 Trade-Off
- 依据是之前做过的需求
- 如果没有依据,怎么做都可以,那么结果就是怎么都不可以
- 需求是 made decision 的重要依据
- 在设计中,经常需要做 Trade-Off
-
-
模型/抽象 思维能力
- 思考的重点:概念,模型,数据结构,算法
- 脱离开代码的细节:函数、语言
- 不是方法间的调用,而是宏观上的模型联系
-
关于接口
-
系统对外的接口,比系统实现本省还要更重要
- 一旦确认不要变更,需要联系相关的人员重新进行系统联调
-
接口定义了功能,如果功能不正确,系统就没有用
-
接口决定了外部关联,相对于内部,外部关系确定后非常难以修改
-
哪些是接口
- 模块对外的函数接口
- 平台对外的API,很多是RPC或WEB api
- 系统间通信的协议
- 系统间存在依赖的数据
-
设计和修改接口,需要考虑的非常清楚
- 合理,好用
- 修改时需要尽量向前兼容
-
第三节 怎么写代码
-
代码也是一种表达方式
-
一个项目中,超过 50%的时间用于沟通
- 沟通有哪些形式
- 开会、邮件、文档、聊天软件、代码
- 代码本身也是一种头痛:别人接手你的代码,跨越时间的沟通
- 如果代码不规范,软件的维护成本高于开发成本
- 现在,代码主要是写给人看的;曾经要为机器考虑很多
- 沟通有哪些形式
-
编程规范,一般包含的内容
- 代码如何规范表达
- 一些语言相关的注意事项
-
理想的场景
- 看别人的代码感觉和看自己的代码一样
- 看代码时能够专注与逻辑,而不是格式方面
- Do not make me think 不要让看代码的人去思考,去想这段代码做了什么
-
模块
- 表现形式,怎样写是一个模块
- 一个 .c 就是一个文件
- 模块是程序的基本组成单位
- 一个文件就是一个模块
- 模块需要有明确的功能
- 紧内聚松耦合
- 模块及模块之间的关系构成
- 模块内部重新调整优化
- 切分模块的一种角度
- 数据类的模块
- 主要完成对数据的封装
- 模块内部变量
- 类的内部变量
- 对外提供明确的数据访问接口
- 数据结构和算法属于模块内部的工作
- 写程序要以数据为中心考虑
- 很多程序最后不好维护都是数据封装没做好
- 系统中有多少数据需要系统管理
- 过程类的模块
- 本身不含数据
- 调用数据类模块或过程类模块
- 模块的重要性
- 好的模块划分是软件架构稳定的基础
- 稳定的模块功能接口 --> 稳定的软件架构
- 减少模块间的耦合 --> 降低软件复杂性
- 清晰地模块有利于代码复用
- 表现形式,怎样写是一个模块
-
类和函数
-
类和函数是两种不同的模型
-
和类的成员变量无关的函数,作为一个独立的函数
- 不建议实现为类的成员函数
- 便于未来的复用
-
-
关于 Ojbect Oriented 面向对象
- OO 是一个好主意,但是真正理解的人不多
- OO 的本质是数据封装
- 写程序应该从Data 开始想问题
- 但是大多数人都是从执行过程开始考虑
- C是面向过程的,C++是面向对象的
- 这是一个普遍的认知错误
- C是基于对象的 Object Based 的,主要是没有多态和继承
- 程序猿的目标是逻辑解决问题
- 不要过多钻语言的特性;语言只是解决问题的方法手段之一
-
模块内部的组成
- 文件头
- 模块的说明,模块功能的简要说明
- 修改历史,修改时间,修改人,修改内容
- 其他必要的,模块级别的说明
- 模块的说明,模块功能的简要说明
- 模块内,内容的顺序尽量保持一致
- 降低阅读的成本
- 文件头
-
函数
- 系统 --> 子系统/程序 --> 模块 -->函数
- 函数描述三要素
- 功能描述:函数是做什么的
- 传入参数描述:含义,限制条件
- 返回值描述:各种可能性
- 函数的规模
- 要足够的短小
- 写好程序的一个秘诀:把函数写的短一些
- bug 往往出现在哪些非常长的函数里
- 如何写的好大家知道,但是都不去坚持
- 函数的返回值
- 每个函数应该有足够明确的含义
- 基于函数的语义,函数返回只有三种类型
- True/false 逻辑判断性
- 操作性 返回成功或失败
- 告知失败类型是什么,异常信息是什么
- 数据获取
- 当出错时返回null
- 如果 null 也是合理的数据时如何处理;返回两个返回值
- 当出错时返回null
- 函数:单入口和单出口
- 是一种推荐的方式
- 多线程前提下可避免出错
-
代码实现需要分行,写注释
-
代码块
-
要把代码的段落分清楚
-
形式的背后是逻辑(划分,层次)
-
标准是什么?不要让我想这段代码做了什么
- 一眼看过去,如果无法看清逻辑,就是不好的代码
- 好的代码不需要你思考太多
- 一定记住
-
注释不是补出来的
- 先写注释,才写代码
-
Btw
- 一个在代码上表达不好的同学,在其他表达上一般也存在问题
-
-
命名
-
命名的范围
- 系统、子系统、模块、函数、变量、常量
-
望名生意
- 概念是建立模型的出发点
-
普遍问题
- 名称不携带信息
- do()
- 携带信息是错误的
- set() vs update()
- 名称不携带信息
-
命名要求
- 准确 易懂
- 名字的可读性,下划线、驼峰
-
-
在互联网时代,系统是运营出来的
-
系统的可检测性非常重要
- 如果没有足够的数据收集,系统等于没有上线
- 提供了跨集群重试的功能,到底生效了多少次
- 埋点或其他方式,查看是否调用
- 类比:产品的上线
- 对一个系统来说,数据和功能同等重要
- 功能可以依靠线下测试来验证
- 数据只能依靠线程的运转和运营
- 在设计和编码阶段,就要考虑系统的运营
- 要有足够状态的记录
- 打日志,
- 日志暴露的信息有限
- 日志采集消耗大量资源
- 系统埋点,对外提供接口
- 提供方便的对外接口
- 打日志,
第四节 修身
- 怎样修炼成为优秀的软件工程师
-
上进的本源
-
三个方面
-
学习 思考 实践
- 学习
- 有太多的经验总结可以借鉴
- 学习的意愿不足
- 很忙
- 不知道学习什么
- stay hungry stay foolish
- 没有学习能力,毫无竞争力
- 思考
- 学而不思则罔
- 不是看了就会,需要去批判去转换
- 经常不思考,就会丧失思考的能力
- 实践
- 知行合一
- 卓有成效的管理者
- 详细记录自己每天的时间安排,每一小时做了什么
- 所有重要的进步,都来源于失败和挫折的经历
- 学习
-
知识 方法 精神
- 知识
- 知识更新很快
- 只学习知识的人,总是感觉世界变化太快
- 编程语言只是一种方法,学会解决问题
- 方法
- 但是方法比知识更有价值,道可道非常道
- 分析问题、解决问题的能力才是最重要的
- 什么是研究
- Research:To identify the Fundamental Promble and solve it 去识别那些最关键的问题并解决他
- 知道什么是最关键的问题
- 精神
- To be or not bo be 永远是一个问题
- 有没有主动去做
- 有没有精神坚持下来
- 前进的路上往往不是鲜花和掌声
- 知识
-
基础乃治学之根本
-
非宁静无以致远
-
求木之长者,必固其根本;欲流之远者,必浚其泉源
-
软件工程师的基础
-
数据结构,算法:操作系统,系统结构,计算机网络
-
软件工程,编程思想
-
逻辑思维能力 归纳总结能力 表达能力
-
研究能力,分析问题,解决问题
-
这个基础的简历,至少也要 5 ~ 8 年之功
-
总是看一些公众号,想快速获取知识,走捷径,是不对的
-
-
-
推荐阅读
- Code complete 代码大全
- Rapid Development 快速开发
- 人月神话
- 201 principlies of software development
- 如何做好 Code Review
- 读书已经成为一道鸿沟
总结
- 认识我们的职业,不是码农,是软件工程师
- 不要忘记我们为了什么而出发
- 不是为了编程,而是改变世界
- 好代码的来源不是写好代码
- 代码是写给别人看的
世运之明晦
人才之盛衰
其表在政
其里在学
– 张之洞
问题
-
在项目研发周期很紧的情况下,没有时间考虑更多的规范的情况如何处理?
- 禁忌:吐槽领导,领导在压缩时间,责任不在我,领导不配合
- 改变现状,努力学习,给出更好的方法;能够成为高层经理的人,会听你讲你的建议;你能够改变的是你自己,学习成长;可能你无法一下做出很大的改变;微小的改变慢慢就会做出重大的改变;很大的程度工期的压力是自己给自己的;命运其实在我们自己的手里
- 合理安排时间,开会时间尽量放在下班后,白天不开会
-
如何和PM沟通?
- 将需求逐个拆解,给出排序,由产品决定谁先谁后
- 学习项目管理
- 有多少事情是做到位了
- 有多少事情是将资源用到对的位置了
- 提升沟通效率
-
做任何项目或事情,设立规范
-
如何提高组合的学习氛围
- 当前产品经理是包工头,分活儿,注重结果,做完就行;关注过程
- 组织学习
- 日常的 code review
- 上次犯的错误还是犯错 人的惯性
- 一个人看完再分享的方法其实并不是很好
- 有KPI压力,关注结果
-
需求是零散的,不是完整的功能模块,敏捷开发快速迭代的如何把握
- 研发没有系统的整理
- 没有系统逻辑梳理清楚
- 缺少文档的沉淀
- 新人无法了解
- 收拾烂摊子 比重建一个系统更困难
-
好的职业生涯发展规划
-
研发
- 偏基础架构
- 偏业务研发
-
角色
- 技术
- LEADER
- Manager
-
六个象限
-
选一个自己感兴趣的业务方向
-
针对自己选择业务方向,继续深挖
-
业务差异的方向非常大
-