《极客时间:代码精进之路》学习笔记
- 认知
- 明确作为一名 Code Reviewer 的职责
- 使用最直观的编码方式
- “好”代码与“坏”代码
- 优秀的代码是“经济”的代码
- 好的代码服务于整个软件生命周期
- 优秀程序员的六个关键特质
- 规范
- 什么是编码规范?
- 为什么需要编码规范?
- 如何给代码起好名字?
- 为什么需要一个好名字
- 为什么需要命名规范?
- 常见的命名方法
- 如何取好名字?
- 小结
- 代码整理的关键逻辑
- 给代码分块
- 使用空白空间
- 一行一个行为
- 写好注释
- 写好声明的八项原则
- 处理好捕获异常
- 经济
- 为什么需要经济的代码?
- 怎样避免过度设计?
- 为什么写代码需要简单直观?
- 如何让写代码更简单直观?
- 如何编写经济的代码?
- 经济代码的检查清单
- 需求评审阶段
- 设计评审阶段
- 代码评审阶段
- 安全
- 为什么需要安全的代码?
- 编写安全代码的基本原则
- 代码质量和工作效率的矛盾如何取舍?
- 结课总结
认知
明确作为一名 Code Reviewer 的职责
一个代码评审者的主要工作,不是批准或者拒绝提交的代码,而是提出合理的建议,帮助代码提交者规避这些失误或者错误,编写出更优秀的代码。
使用最直观的编码方式
坚持使用最直观的编码方式,而不是追求代码简短,可以避免很多不必要的错误。减少错误、节省时间,是我们现在选择编码方式的一个最基本的原则。
比如在使用条件语句 if
和使用条件运算符 ?:
时,不要纠结用哪个更好,也不要追求简短使用条件运算符,当其中的逻辑比较复杂时,可读性就比较差了。
“好”代码与“坏”代码
“好”代码与“坏”代码并没有统一的衡量标准,但有一些经验值得我们学习借鉴:
“好”的代码应该:
- 容易理解;
- 没有明显的安全问题;
- 能够满足最关键的需求;
- 有充分的注释;
- 使用规范的命名;
- 经过充分的测试。
“坏”的代码包括:
- 难以阅读的代码;
- 浪费大量计算机资源的代码;
- 代码风格混乱的代码;
- 复杂的、不直观的代码;
- 没有经过适当测试的代码。
优秀的代码是“经济”的代码
优秀的代码应该是投入少、收益大、投资回报高。
“经济”的代码行为:
- 代码写得又快又好;
- 代码跑得又快又好;
- 代码写得精简易懂。
“不经济”的代码行为:
- 代码写得快但错误多;
- 代码跑得快但安全问题多;
- 代码写得精简但没人看得懂。
好的代码服务于整个软件生命周期
写代码时应该跳出开发视角,从整个软件的目标思考,能服务于软件目标,适合软件目标的,对于整个软件生命周期都是经济的才是好的代码,而不是说代码中用了某个高深的技巧或技术就是好代码。
优秀程序员的六个关键特质
- 掌握一门编程语言;
- 解决现实的问题。程序员的存在不是为了写代码,而是为了解决现实问题,实现现实价值;
- 发现关键的问题;
- 沉静的前行者。完美是不存在的,所以我们才追求完美,对完美的过分追求,可能是一个代价高昂,收获甚小的行为;
- 可以依赖的伙伴。能够在团队中快速学习、成长,变得越来越优秀,也能够帮助其他团队成员变得越来越优秀;
- 时间管理者。要坚持做需要做的事情。不需要的、不紧急的、价值不大的,我们可以暂时搁置起来。一个人,能做的事情是有限的,能把最重要的事情最好,就已经很了不起了。
规范
什么是编码规范?
编码规范指的是针对特定编程语言约定的一系列规则,通常包括文件组织、缩进、注释、声明、语句、空格、命名约定、编程实践、编程原则和最佳实践等。一般而言,一份高质量的编码规范,是严格的、清晰的、简单的,也是权威的。
为什么需要编码规范?
编码规范可以帮我们选择编码风格、确定编码方法,以便更好地进行编码实践。 简单地说,一旦学会了编码规范,并且严格地遵守它们,可以让我们的工作更简单,更轻松,少犯错误。这个问题弄明白了,我们就能愉快地遵守这些约定,改进我们的编程方式了。
- 规范的代码,可以降低代码出错的几率。复杂是代码质量的敌人。 越复杂的代码,越容易出现问题,并且由于复杂性,我们很难发现这些隐藏的问题。
- 规范的代码,可以提高编码的效率。
- 规范的代码,降低软件维护成本。
- 编码规范越使用越高效。
如何给代码起好名字?
为什么需要一个好名字
名字要准确地代表它背后的东西,并且还能让代码干净漂亮。不然,我们的思路就会受到干扰,影响我们的思考和心情。
名字就是沟通的方式,错误的命名很难让我们清楚地理解代码真实的意图。所以,混淆的命名很难让我们阅读和理解代码。
为什么需要命名规范?
- 为标识符提供附加的信息,赋予标识符现实意义。帮助我们理顺编码的逻辑,减少阅读和理解代码的工作量;
- 使代码审核变得更有效率,专注于更重要的问题,而不是争论语法和命名规范这类小细节,提高开发效率;
- 提高代码的清晰度、可读性以及美观程度;
- 避免不同产品之间的命名冲突。
常见的命名方法
- 驼峰命名法(CamelCase);
- 蛇形命名法(snake_case);
- 串式命名法(kebab-case);
- 匈牙利命名法。标识符由一个或者多个小写字母开始,这些字母用来标识标识符的类型或者用途。标识符的剩余部分,可以采取其他形式的命名法,比如大驼峰命名法。
如何取好名字?
一、要有准确的意义
名字要能够准确、完整地表达出它代表的意义,可以见字知意,名副其实。比如,表达式 a = b - c
的语法是没有什么问题,可是该表达式代表的实际含义并不清楚。相比而言,grossIncome = grossRevene - costOfGoodsSold
就有很准确、清晰的现实意义。这样的命名更容易阅读和理解。
二、严格遵守命名规范
不同的编程环境,偏爱不同的命名规范,比如 Java 倾向于使用驼峰命名法,C 语言倾向于使用蛇形命名法,CSS 使用串式命名法。 尽管如此,如果定义了个性化的命名规范,请严格遵守自定义的命名规范,如果没有定义个性化的命名规范,我们就需要严格遵守业界普遍公认的命名规范。
三、可读性优先
- 可读性强的名字优先于简短的名字,尽量使用完整的词汇。
- 不要使用缩写、简写、缩略词,除非这些词语被广泛使用。
- 不要使用太短的名字,比如一个字母,除非是广泛接受的特例(i/j/k/m/n 表示临时使用的整数,c/d/e 表示临时使用的字符)。
- 避免含糊、混淆或者误导。
- 不要混合使用英文和汉语拼音。由于很多类库使用的是英文,如果使用汉语拼音命名,会造成事实上的拼音名字与英文名字的混用,所以也要尽量避免使用拼音命名。
小结
简言之,取名字要做到“信、达、雅”(准确、直观、优美)。“信”和“达”是基本要求,有才气的你可以有“雅”的追求。
代码整理的关键逻辑
给代码分块
- 保持代码块的单一性,一个代码块只能有一个目标。代码块内所有的内容都是为了一个目标服务的,不能把无关的内容放在同一个代码块里。同一个代码块里语句的相互联系比与相邻代码块里的语句关系更为紧密;
- 注意代码块的完整性。代码块是一个完整的信息块。一个代码块要表达一个相对完整的意思,不能一个意思没说完就分块了,就像话说了半句一样;
- 代码块数量要适当。代码块过多,会让人觉得路径太长,逻辑复杂,不容易阅读理解。一个基础的代码块最好不要超过 25 行(通常显示屏小半个页面),否则就会有增加阅读理解的困难。
使用空白空间
- 同级别代码块靠左对齐;
- 同级别代码块空行分割;
- 下一级代码块向右缩进;
- 同行内代码块空格区隔。
一行一个行为
基本的换行原则
- 逗号后换行;
- 在操作符前换行;
- 高级别的换行优先;
- 新的换行与上一行同级别表达式的开头对齐;
写好注释
在理想状况下,代码不需要注释。理想的代码,命名恰当,结构清晰,逻辑顺畅,含义显而易见。
如果使用准确、有意义的命名,我们就可以去掉没有意义的注释了。
如果一段代码不再需要,我会清理掉代码,而不会保留这个注释掉的代码块。不要在源代码里记录代码历史,那是代码版本管理系统该干的事情。
注释的三项原则
- 准确,错误的注释比没有注释更糟糕。
- 必要,多余的注释浪费阅读者的时间。
- 清晰,混乱的注释会把代码搞得更乱。
写好声明的八项原则
基本两大原则:取好名字、容易识别。
- 取一个好名字;
- 一行一个声明。不在同一行里声明多个变量,即使这一行很短,也不要在同一行声明不同类型的标识符;
- 局部变量需要时再声明。标识符的声明应该和它的使用尽可能地靠近,特别是局部变量的标识符声明。这样在视觉上,标识符的定义和使用,可以方便我们阅读和记忆;
- 类属性要集中声明。同样是为了阅读和记忆,类变量的声明则要集中。因为类变量无论是私密变量,还是公开变量,在类的方法实现中,随时都可以调用。我们需要把这些变量放在一起,以便于修改和查找;
- 声明时就初始化。除非变量的初始值依赖于更多的条件,或者涉及到一定的计算,否则,声明时就应该完成初始化。声明时初始化,可以防止初始化的遗漏或者不必要的代码重复;
- 尾随的花括号。一般来说,类声明和方法声明后,要使用花括号把实现的代码包括进来。左括号不要单独成行,要紧随在语句尾部,以一个空格隔开;右括号单独一行;
- 靠紧的小括号。小括号的使用语法也可以很随意。小括号一般用来识别一个标识符是不是方法标识符,所以建议小括号要紧靠着标识符,中间不要有空格。
- 搜索优化的换行。搜索优化是我们编写代码时要考虑的一个因素。搜索优化既包括针对搜索引擎的优化(SEO),也包括针对编辑器(vi, Netbeans)以及系统工具(grep)的搜索优化。
处理好捕获异常
- 不要使用异常机制处理正常业务逻辑;
- 异常的使用要符合具体的场景;
- 具体的异常要在接口规范中声明和标记清楚。
经济
为什么需要经济的代码?
- 提升用户体验。一致性的性能体验,是软件产品赢得竞争的关键指标。复杂的,反应迟钝的软件,很难赢得用户的尊敬。
- 降低研发成本。通过降低软件的复杂度,提高软件的复用,提前考虑性能问题,可以降低软件研发成本,缩短软件开发周期。
- 降低运营成本。经济的代码可以降低软件的复杂度,提高计算资源的使用效率,降低运营成本。
- 防范可用性攻击。复杂的代码和性能低下的代码,更容易成为黑客攻击的目标。如果一个服务器,需要耗费很多资源才能处理一个请求,那么数量很少的模拟请求攻击,就可以导致服务器瘫痪。
怎样避免过度设计?
- 识别核心需求。从用户角度知道什么是核心需求,什么是衍生需求,什么是无效需求;
- 设计不要追求一步到位,有限的时间内做最有价值的事情;
- 设计时,可以时刻问自己:什么是必须做的?什么是现在就必须做的?
为什么写代码需要简单直观?
- 快速行动的唯一办法,好的程序员不仅是关键时刻能够救火的队员,还是从一开始就能消除活着隐患的队员。
- 减轻沟通成本,使代码易于理解;
- 降低软件复杂度,从而降低软件风险;
如何让写代码更简单直观?
- 使用小的代码块;
- 一个代码块只做一件事情;
- 遵守约定的惯例;
- 花时间做设计,不要一开始就写代码;
- 减少接口给之间的依赖关系;
- 接口使用方式要“傻”,所有接口的设计,都是为了最终的使用。方便、皮实的接口,才是好用的接口;
- 从真实问题开始,把大问题逐层分解为“相互独立,完全穷尽”的小问题
- 问题的分解过程,对应的就是软件的接口以及接口之间的联系;
10.一个接口,应该只做一件事情。如果做不到,接口间的依赖关系要描述清楚。
如何编写经济的代码?
- 避免过度设计。
- 选择简单直观。设计一个简单直观的接口,一个接口只应该做一件事情,如果这个情况太理想化,就要想办法减少接口的依赖关系。
- 超越线程同步。尽量使用异步编程来避免线程同步,提高程序响应时间。
- 减少内存使用。
- 规避性能陷阱。
- 规模扩张能力。
经济代码的检查清单
需求评审阶段
- 需求是真实的客户需求吗?
- 要解决的问题真实存在吗?
- 需求具有普遍的意义吗?
- 这个需求到底有多重要?
- 需求能不能分解、简化?
- 需求的最小要求是什么?
- 这个需求能不能在下一个版本再实现?
设计评审阶段
- 能使用现存的接口吗?
- 设计是不是简单、直观?
- 一个接口是不是只表示一件事情?
- 接口之间的依赖关系是不是明确?
- 接口的调用方式是不是方便、皮实?
- 接口的实现可以做到不可变吗?
- 接口是多线程安全的吗?
- 可以使用异步编程吗?
- 接口需不需要频繁地拷贝数据?
- 无状态数据和有状态数据需不需要分离?
- 有状态数据的处理是否支持规模水平扩张?
代码评审阶段
- 有没有可以重用的代码?
- 新的代码是不是可以重用?
安全
为什么需要安全的代码?
- 代码质量是信息安全的基础。大部分的信息安全事故,是由软件代码的安全缺陷引起的。没有安全质量保证的代码,建立不起有效、可信的信息系统。信息系统的安全,主要依赖的不是信息安全技术专家,而是我们每一个编写代码的工程师。
- 安全漏洞的破坏性难以预料。直到真实的安全问题发生之前,我们都难以预料软件的安全漏洞到底有多大的破坏性。一个小小的安全漏洞,如果被攻击者抓住了时机,就可以瞬间摧毁多年的苦心经营和良好声誉,把公司推到舆论的风口浪尖,甚至使公司面临毁灭性的风险和挑战。
- 安全编码的规则可以学得到。由于安全攻击技术的快速发展,安全编码涉及到的细节纷繁复杂,安全问题的解决甚至需要大规模、大范围的协作。编写安全的代码不是一件轻而易举的事情。但是,安全编码的规则和经验,却是可以学习和积累的。使用必要的安全管理工具,开展代码评审和交流,也可以加速我们的学习和积累,减少编写代码的安全漏洞。
要想掌握安全编码的技术,熟练修复软件漏洞的实践,我们需要跨过意识、知晓、看到三道关卡。面对最新的攻击技术和安全问题,通过每一道关卡都障碍重重。我们要主动地跟踪安全问题的最新进展,学习最新的安全防护技术。及时更新自己的知识,掌握难以学习到的知识和技能,也是构建和保持我们竞争力的一个重要办法。
编写安全代码的基本原则
- 清楚调用接口的行为使用不恰当的接口,是代码安全风险的主要来源之一。我们一定要了解、掌握每一个调用接口的行为规范,然后在接口规范许可的范围内使用它们。不要去猜测接口的行为方式,没有明文规定的行为,都是不可靠、不可信的行为。
- 跨界的数据不可信任。跨界的数据面临两大问题:一个问题是数据发送是否可信?另一个问题是数据传递过程是否可靠?这两个有任何一个问题不能解决,跨界的数据都可能被攻击者利用。因此使用跨界的数据之前,要进行校验。
- 最小授权的原则。信息和资源,尤其是敏感数据,需经授权,方可使用。所授予的权力,能够让应用程序完成对应的任务就行,不要授予多余的权力。
- 减小安全攻击面。减小、简化公开接口,缩小可以被攻击者利用的攻击界面。比如,设计更简单直观的公开接口,使用加密的数据传输通道,只对授权用户开放服务等等,这些措施,都可以减少安全攻击面。
- 深度防御的原则。使用纵深防御体系防范安全威胁。要提供深度的防御能力,不能仅仅依靠边界的安全。编写代码,要采用谨慎保守的原则,要解决疑似可能出现的安全问题,要校验来源不确定的数据,要记录不规范的行为,要提供安全的应急预案。
代码质量和工作效率的矛盾如何取舍?
以下内容引用自《极客时间:代码精进之路》—— (Q&A加餐丨关于代码质量,你关心的那些事儿)中的内容:
这个问题有一个隐含的假设,就是代码质量和工作效率不可兼得。这本身是个常见的误区。这个误区也给了我们一个看似成立的借口:要么牺牲代码质量,要么牺牲工作效率。
代码质量和工作效率,是不是矛盾的呢?这取决于我们观察的时间、地点以及维度,甚至我们是怎么定义效率的。如果给我们一个小时的时间,看看谁写的代码多。不顾及代码质量的也许会胜出(只是也许,我们后面再说为什么只是也许);认真设计、认真对待每一行代码的也许会败北(也只是也许)。
短期内代码写得多与否,我们可以把这个比喻成“走得慢,还是走得快”的问题。
如果给我们半年的时间,那些质量差的代码,编写效率也许可以和质量好的代码保持在同一水准,特别是软件还没有见到用户的时候。如果给我们一年的时间,软件已经见到了用户,那么质量差的代码的编写效率,应该大幅度落后于优质代码了。甚至生产这些代码的团队,都被市场无情淘汰了。
看谁的代码能够长期赢得竞争,我们可以把这个比喻成“到得慢,还是到得快”问题。
为什么会这样呢? 一小时内,什么都不管,什么都不顾,怎么能不多产呢!可是,不管不顾,并不意味真的可以高枕无忧。需求满足不了就会返工,程序出了问题也会返工,测试通不过还会返工······每一次的返工,都要你重新阅读代码,梳理逻辑,修改代码。有很多时候,你会发现,这代码真是垃圾,没法改了,只有推倒重来。
这个时候再回过头看看这种代码编写的背景,你能说这是一种高效率的行为吗?这就相当于,一个马拉松比赛,前 1000 米你在前头,后来你就要往回跑。1000 米这个槛,有人跑一次就够了,你要是跑七八次,还谈什么效率呢。这种绝望的事情看似荒唐,其实每天都会发生。为什么会这样呢? 因为在软件开发的过程中,遗留的问题需要弥补,这就类似于往回跑。所以,走得快,不一定到得快。
你不妨记录一下三个月以来,你的工作时间,看看有多少时间是花在了修修补补上,有多少时间是花在了新的用户需求上。这样,对这个问题可能有不一样的感受。
另外,是不是关注代码质量,就一定走得慢呢?
其实也不是这样的。比如说,如果一个定义清晰,承载功能单一的接口,我们就容易理解,编码思路也清晰,写代码就又快又好。可是,简单直观的接口怎么来?我们需要花费大量的时间,去设计接口,才能获得这样的效果。
为什么有的人一天可以写几千行代码,有的人一天就只能写几十行代码呢?这背后最重要的一个区别就是心里有没有谱,思路是不是清晰。几千行的代码质量就比几十行的差吗? 也不一定。
你有没有遇到这样的例子,一个同学软件已经实现一半了,写了十万行代码。另一个熊孩子还在吭哧吭哧地设计接口,各种画图。当这个同学写了十五万行代码的时候,完成一大半工作的时候,那个熊孩子已经五万行代码搞定了所有的事情。你想想,效率到底该怎么定义呢?
那个熊孩子是不是没有能力写二十万行代码呢?不是的,只要他愿意,他也许可以写得更快。只是,既然目标实现了,为什么不去聊聊天,喝喝咖啡呢?搞这么多代码干啥!你想想,效率能用代码数量定义吗?
就单个的程序员而言,代码质量其实是一个意识和技能的问题。当我们有了相关的意识和技能以后,编写高质量的代码甚至还会节省时间。如果我们没有代码质量的意识,就很难积累相关的技能,编写代码就是一件苦差事,修修补补累死人。
有很多公司不愿意做代码评审,效率也是其中一个重要的考量。大家太注重一小时内的效率,而不太关切一年内的效率。如果我们将目光放得更长远,就会发现很多不一样的东西。
比如说代码评审,就可以减少错误,减少往回跑的频率,从而节省时间。代码评审也可以帮助年轻的程序员快速地成长,降低团队出错的机率,提高团队的效率。
有一些公司,定了编写规范,定了安全规范,定了很多规范,就是执行不下去,为什么呢? 没有人愿意记住那么多生硬的规范,这个时候,代码评审就是一个很好的方法,有很多眼睛看着代码,有反馈,有讨论,有争议,有建议,团队能够更快地形成共识,找出问题,形成习惯,共同进步。看似慢,其实快。
英文里,有一句经典的话 “Run slowly, and you will get there faster”。汉语的说法更简洁,“因为慢,所以快”。
一般情况下,通常意义上的软件开发,如果我们从产品的角度看,我认为高质量的代码,会提升工作的效率,而不是降低工作效率。
当然,也有特殊情况。比如我们对质量有着偏执般的追求,这时候,效率就不是我们的首选项。也有情况需要我们在五秒以内眨眼之间就给出代码,这时候,质量也不是我们的首选项。
代码的质量该怎么取舍呢?这取决于具体的环境,和你的真实目标。
结课总结
总体看下来还是比较适合写 Java 的同学,其中有很多关于 Java 的案例分析与技巧分享,但也有一些通用的编码实践与思想值得学习,感兴趣的同学可以自己去看看,(这是链接)
上述学习笔记若有描述不对的地方,或者你有任何分享,欢迎留言。