【读后总结】程序员修炼之道---从小工到专家

1,288 阅读13分钟

[TOC]

【读后总结】程序员修炼之道---从小工到专家

一,个人读后总结

本书相对比较基础,不是那种大规模程序设计、高并发设计等等,主要是针对程序员的一些基本素质和一些基本常规编程设计做一些梳理和规范,对于初入职的程序员,养成这些良好素质是非常有必要的;对于已经入职多年的程序员,回顾一下本书,然后结合自身情况看看是否能够基本达到本书中的一些素养也是有必要的。

整体而言,有一定的经验性总结,相对来说比较基础,对开发者也有一定的作用;对我个人而言,里面很多的素养、设计规范之类都有一定了解,不过可能平时做的不够彻底,因此看完之后,还是有一定的收获,至少有了这样的文档性的总结,方便后续快速检阅查看。

二,程序、架构设计方面

1,避免重复,进行复用

复用可以减少代码臃肿,也可以降低改动风险(一个改动,要改动多处,容易遗漏),增加开发效率。如下几个情况可能会使得有重复的代码,需要注意尽量避免:

  • 代码模块功能类似
  • 不同语言要实现同一功能
  • 不同开发者实现同一个类似功能

2,灵活的设计、隔离性

项目开发中,经常会遇到某个功能需求改变,或者某个底层资源改变,或者数据结构的改变,要做到撤销的最小代价,注意项目永远没有最终最确定的抉择

代码架构的设计要保持灵活性、隔离性、可替代性;如某种负载均衡算法的替换,如istio数据平面的替换等。

这个架构设计需要:

  • 解耦

  • 接口抽象

    • 抽象出各种不同接口,如虚基类、纯虚基类、接口和实现
  • 隐藏细节

    • 接口暴露的方式,需要隐藏一些私有的细节处理,对外暴露统一的外部方法
  • 隔离

    • 如何保证互不影响,互不管理
  • 元数据方式

3,解耦合【正交性】

划分出细粒度的模块,然后各个模块之间解耦合,比如有一个函数的得墨忒耳法则.得墨忒耳定律也叫做“最少了解原理”,是一种软件设计原理,尤其是应用到面向对象的程序设计中,基本原理为:

  • 每个对象对其他对象只能有最少的了解:只有总体才能接近个别对象;
  • 每个对象只能和自己的朋友对话:不要和陌生人说话;
  • 只和自己最亲密的朋友对话。

一定尽量不要开发设计出强耦合的系统,要划分模块、分层设计,这样可以提高效率,也能降低风险,否则,会牵一发而动全身。最简单的如MVC架构模型,上层应该可以支持多种形态,改动一个模块应该只影响另外一个模块的调用而不是影响全局

每个模块的设计需要有暴露给外部的,同时也会需要有私有的。解耦合的设计的理念就是:

  • 分层
  • 分模块
  • 抽象

4,分层架构设计

程序架构要分层,分而治之,划分不同功能的子模块;划分模块之后,就要考虑模块(子服务)之间如何通信问题,常用的方案如:

  • pub/sub:发布订阅模式

    • 只对感兴趣事件进行订阅
  • MVC模式

    • Model-View-Controller

5,元数据的设计

用元数据(metadata)描述应用的配置选项、参数、用户偏好等,很多程序设计中都用到了元数据,尤其是一些开源的大型项目中,那么元数据到底是啥?元数据是指数据的数据,广义上就是任何对应用描述的数据。如数据库schema或数据词典。schema含有名称、存储长度等属性进行描述的属性,我们应该要可以访问和操作这些信息,就像对数据库中任何其他数据一样。

典型情况系,元数据应该在运行时而不是编译时被访问和使用。优雅的方式是使用元数据进行数据配置和驱动应用,达到这样的目标:声明式思考,也即是规定要做什么而不是怎么做,并创建高度灵活的程序。核心设计思想就是将抽象放进代码,细节放进元数据,这样的优势在于:

  • 设计必须要解耦合,这样会更灵活

  • 需要创建更健壮、更抽象的设计;细节的处理推迟到程序之外

    • 这个,我还没有理解明白
  • 可定制,如插件化,无需修改程序

6,软件程序的并发和次序

时间是软件架构的一个经常被忽略的方面,这里的时间指的是程序自身的时间因素:

  • 并发:同一时间发生多个事件
  • 次序:事件在时间中的相对位置

尤其是互联网的开发设计中,并发是一个不可忽视的因子,设计之初就必须要考虑进来;如何防止并发冲突?如何保证高并发?

防止并发冲突,改善并发性,一个可能的原则就是分析工作流,可以通过UML活动图去分析。

另外,并发方面的编程,要考虑到多进程、多线程、多协程;服务端架构设计还要考虑高并发,如何扩缩容等

7,原型->基础框架->逐渐填充

当需求并不明朗的时候,或者当需要从0到1的时候,可以先快速搭建一个基础框架,实现一个最简单的诉求,当然需要划分模块、分层等基础设计,不用太过复杂,先实现效果,然后逐渐填充和丰富,小版本重构和优化,最终形成一个大的项目

前期一定需要原型的配合,先构建原型出来,然后再逐步迭代优化,需要注意:

  • 正确性
  • 完整性
  • 健壮性
  • 风格

原型设计中需要考虑:

  • 组件的定义和责任划分是否清晰
  • 耦合是否最小、是否细粒度
  • 接口定义、约束是否满足诉求

8,进行预估和评审

任何项目都需要进行预估,预估一下整体时间、风险点、项目大小等;预估完了之后,进行初步设计,设计了之后需要再进行评审:

  • 拆分组件、拆分模块
  • 细粒度组件、子模块的预估
  • 项目进度的预估
  • 项目风险的预估
  • 需求的评审
  • 项目的评审
  • 架构设计的评审

三,程序编码方面

要不断批判所有代码,包括自己的和别人的!!!

1,理解代码的工作原理

  • 不要盲目的写代码,要先理清原理、需求、设计等等,最后才是编码

  • 要制定阶段性的规划

  • 思考所有可能的边界并处理好所有边界条件,不要靠猜

  • 每一行代码都要清楚含义,确保自己写的代码就是自己要表述的流程,对代码白盒化

  • 严格的测试用例、正常条件、异常条件

  • copy原有代码的时候,想想,是否能够优化?原有代码是否合适?

2,理清系统性能

要时刻估算程序的运行情况,QPS=10w如何,QPS=100W如何? QPS=1000W的时候又当如何?因为随着变化,大多数情况下都不是线性变化的。估算的范围包括但不限于:系统的处理时间、占用内存、CPU、磁盘io等

那如何估算?除了已有知识和经验能够帮助估算外,还可以进行建模估算:

  • 循环
  • 嵌套循环
  • 二分法
  • 分而治之
  • 组合

3,代码随时重构

架构需要优化,代码也随时需要优化,前期的代码总会有优化之处

当代码出现重复、过时的知识、性能问题、耦合性太强的时候就必须要进行重构;而且我们需要经常性的重构并且要早早的开始重构,否则越到后面,历史包袱越大;每次回归自己的代码,都要考虑,能否重构,可否更优?

如果后面一个项目copy或者沿用前一个项目,那么后面的项目,一定要比前面的项目架构要更优,一定要做出改变,否则就不会有成长。

重构的时候需要注意一些技巧如:重构的时候不要新增功能,重构之前需要有较为全面的单元测试用例,重构完了之后需要全面测试

4,编写易于测试的代码和丰富的测试用例

单元测试、集成测试

小模块的测试

丰富的测试用例

示例代码

5,按合约设计(DBC)

人不可能写出十分完美的软件,不符合预期的程序、接口时有发生,我们需要防卫性的编码,因此就是对方的都不可信任,比如输入参数。

按合约设计的思想在于充分利用前置条件、后置条件、类不变项。其实意思就是一切要按照事先约定的合约进行,如何保证呢?那么必须在设计的时候考虑:输入域的范围、边界条件是如何、输出数据是怎样、可以输出什么不可以输出什么。

一些好的姿势包括:断言、预处理、提前返回错误和崩溃。关于提前返回错误和崩溃的意义在于对于输入的检测上,要保证最初输入的异常就返回而不用等到程序开始计算运行之后才返回

6,程序发生奔溃的处理

程序发生错误、奔溃是无法避免的,但是我们需要知道,如果错误已经发生,那么代表程序一定在某些地方存在问题,尤其是一些无法必现的问题,那么就需要我们在错误发生之后能够有足够的信息去分析它并fix它。检查每一个可能的错误,尤其是意料之外的错误是一种良好的实践。

如果程序发生一些极端错误之后,如果没有终止程序,很有可能导致后端数据产生脏数据或者后端请求出现批量异常等等。因此需要及早抛出异常

通过断言来检测输入参数,在c、c++中是常用的优雅姿势,但是不要用断言去处理真正的错误流程

处理好错误流程和异常处理流程、捕获异常并进行处理,但是需要区分什么情况下才是真正的异常,这个可能需要根据实际情况去分析

7,解决疑难杂症问题的思路

要知道,任何问题都有解决方案,只是你暂时没有找到,或者没有找到合适的。排查问题的方法:梳理出所有可能的路径,先不要排除任何东西,然后逐一检查并排除。

对于疑难杂症的重要的思考点:

  • 这个问题是思路是否正确?注意力在这上面是否正确?
  • 是否有更容易的方法?
  • 为什么如此难以解决?
  • 必须是这种方式解决吗 ?
  • 这个为什么是一个问题 ?
  • 真的必须解决吗 ?

四,项目管理相关

1,需求相关

项目是否能够成功?项目的各种需求?

业务项目而言,会有产品经理提需求;基础项目而言,需要我们开发人员自己挖掘需求、搜集需求,其中最重要就是挖掘需求

一个典型例子:

  • 只有指定人员才能查看档案【good】
    • 这样的需求过来,开发人员会设计一套权限管理系统,方便后续扩展
  • 只有领导和人事部门才能查看档案【bad】
    • 这样的需求过来,开发人员可能只是做一个简单的配置,进行前置静态检测
    • 只要需求改变,就可能无法满足,甚至需要修改代码

因为这就告诉我们几点:

  • 需求很有可能是经常变动的,需求是否合理,如何满足日益变化的需求
  • 需求的陈述表达
  • 设计的时候一定要考虑后续的兼容性和扩展性
  • 抽象很重要

2,思考清楚再开始启动项目

如果有疑惑,一定要反复思考清楚,等一起都思考清楚,准备就绪后再开始编码、项目设计。

因为一旦没有思考清楚,很可能导致返工,或者是无用功,就怕就是折腾一两个月后发现前面都做错了!

3,程序的规范

程序需要有一定的规范,尤其是多人协作的程序

4,自动化工作流程

工作流程自动化、项目自动化,构建、编译、部署等;这个在我们目前现有系统和工作中基本已经都是自动化了,如CI、CD等

5,各种类型的测试

自动化测试,早测试、常测试,测试占编码很大一部分;项目需要单元测试、集成测试;需要有一定的覆盖度。主要测试种类包括:

  • 单元测试
  • 集成测试
  • 验证和校验
  • 性能测试
  • 可用性测试
  • 资源耗尽、错误和恢复

6,文档和注释

关于文档方面,很重要,但是经常会发现文档跟不上代码节奏,可以将代码生成文档。另外就是代码注释,注释也经常跟不上代码节奏。因此需要注意文档和注释的编写、及时更新等

五,其他零散经验

  • 对象属性的读写不要直接暴露字段,应该用方法封装,方便未来调整,也能够保持一致性

  • 检查每一个可能的错误,尤其是意料之外的错误是一种良好的实践。

  • 通过断言来检测输入参数,在c、c++中是常用的优雅姿势,但是不要用断言去处理真正的错误流程

  • 元数据的设计

【"欢迎关注我的微信公众号:Linux 服务端系统研发,后面会大力通过微信公众号发送优质文章"】

我的微信公众号