这本书从Unix的设计理念等各方面讲起,内容充实有趣,尽管介绍细节的部分对于不太了解Unix的人呢来讲有些生涩,但在道的介绍上有不少可取之处。由于某人的出现,生活中多了新的追求,比想象中多用了一些时间看完了它。下面摘取一些其中精华的观点,力图尽量还原书中的本意。

Context

哲学

  • 每过18个月,就有一半的知识会过时
  • X致力提供一套“机制,而不是策略”
  • 提供机制而不是方针的哲学让Unix长期保鲜
  • Unix传统将重点放在尽力使各程序接口相对小巧、简洁、正交
  • Unix管道发明人Doug McIlroy曾说过:
    • 让每个程序就做好一件事
    • 假定每个程序的输出都会成为另个程序的输入
    • 尽早将设计和编译的软件投入使用
    • 优先使用工具而不是拙劣的帮助来减轻编程任务的负担
  • Rob Pike曾从不同的角度表述了Unix的哲学
    • 你无法断定程序会在什么地方好费时间,所以别急于找地方改代码,除非你已经证实那儿就是瓶颈所在
    • 没对代码估量最耗时的部分前,别去优化速度
    • 花哨的算法在n很小的时候通常很慢,而n一般很小
    • 花哨的算法比简单算法更容易出bug、更难实现
    • 编程的核心是数据结构,而不是算法
  • 书中对Unix的哲学,总结为下面这些点
    • 模块原则,使用简洁的接口拼接简单的部件
    • 清晰原则,清晰胜于机巧,程序是写给人看的
    • 组合原则,设计时要考虑拼接组合
    • 分离原则,策略和机制分离,接口和引擎分离
    • 简洁原则,设计要简洁,复杂度能低则低
    • 吝啬原则,除非没有办法,不要写大程序
    • 透明性原则,设计要有可见性(如输入输出、打点上报),便于审查和调试
    • 健壮原则,健壮源于透明和简洁
    • 表示原则,把知识转移到数据中,保证逻辑的简朴而健壮
    • 通俗原则,即最少惊奇原则,接口设计避免标新立异,缓和学习曲线
    • 缄默原则,设计良好的程序将用户的注意力视为有限的宝贵资源
    • 补救原则,出现异常时,马上退出并给出错误信息
    • 经济原则,宁花机器1分,不花程序员1秒
    • 生成原则,避免手工hack,编写程序去生成程序
    • 优化原则,过早优化会妨碍全局优化,先制作原型,再精雕细琢;先可用,再优化
    • 多样原则,不相信“不二法门”的断言
    • 扩展原则,未来总比预想来得要快
  • Unix哲学一言以蔽之,即KISS(Keep It Simple, Stupid!)
  • 善用他人写好的工具,尽可能将一切自动化

历史

  • 计算机不应仅被视为一种逻辑设备而更应视为社群的立足点
  • 1985年IEEE支持的POSIX标准表述了BSD和SVR3(System V Release3)调用的交集
  • 1987年初,GNU C编译器第一版问世
  • 1995年,Linux找到自己的杀手级应用——开源的web服务器Apache
  • 过度依赖任何一种技术或者商业模式都是错误的

Unix哲学和其他哲学的对比

  • 统一性理念:一切皆文件 & 管道概念
  • 多任务能力:抢先式多任务
  • 协作进程:低价的进程生成和简便的进程间通讯
  • 内部边界:程序员最清楚一切
  • 文件属性和记录结构:没有文件属性
  • 减少使用不透明的二进制文件格式
  • 首选CLI命令行界面
  • Unix是程序员写给程序员的
  • 开发的门槛:轻松编程
  • 操作系统的比较
    • MacOS:MacOS有一个自己的界面方针,非常详细地说明了应用程序GUI的表现形式和行为模式
    • Windows NT:有注册表蠕变现象,不过支持了Cygwin,实现了Unix API的兼容
    • MVS:一切皆批处理
    • Linux:贴近终端用户的愿望使得Linux开发者比专有Unix更注重系统安装的平稳性和软件发布问题

Design

模块性

  • 展开来说就是,要编写复杂软件又不至于一败涂地的唯一方法就是用定义清晰的接口把若干简单模块组合起来
  • Unix程序员骨子里的传统是:更加笃信重视模块化、更注重正交性和紧凑性等问题
  • 封装良好的模块不会过多向外披露自身的细节
  • 缺陷个数随着模块的代码行数会先减小,随后按代码行数的平方上升
  • 紧凑性和正交性
    • 人类短期记忆能够容纳的不连续信息数就是7,加2或减2
    • 紧凑性:有经验的用户通常不需要操作手册,让人乐于使用,不会在想法和工作间格格不入;紧凑不等于薄弱;一个功能子集,能够满足专家用户80%以上的一般需求
    • 正交性:任何操作均无副作用,改变每个属性的方法有且仅有一个;重构代码就是改变代码的结构和组织,而不改变其外在行为
    • 任何一个知识点在系统内都应当有一个唯一、明确、权威的表述(Single Point of Truth, SPOT)
    • 提高紧凑性的精妙但强大的办法就是围绕“解决一个定义明确的问题”的强核心算法组织设计,避免臆断和捏造。形式化往往能极其明晰地阐述一项任务,与形式法相对的是试探法——凭经验法则得到的解决方案,这种思路的问题是回增生出大量特例和边界情况
  • “限制不仅提倡了经济性,而且某种程度上提倡了设计的优雅”。要达到这种简洁性,尽量不要去想一种语言或操作系统最多能做多少事情,而是尽量去想这种语言或操作系统最少能做的事情——不是带着假想行动
  • 设计有自顶向下和自底向上两种思路,前者通常先考虑主事件循环,再插入具体事件;后者通常先考虑封装具体任务,再按次序粘合在一起
  • 出于自我保护,程序员尽量双管齐下——一方面以自顶向下的应用逻辑表达抽象规范,另一方面以函数或库来手机底层的域原语(原子操作)
  • 实际代码往往是自顶向下和自底向上的综合产物。同一个项目经常同时兼有,这就导致了“胶合层”的出现
  • 胶合层是个挺讨厌的东西,必须尽可能薄,这一点极为重要。薄胶合层原则可以看做是分离原则的升华。策略(应用逻辑)应该与机制(原子操作集)清晰地分离和解耦。
  • “完美之道,不在无可增加,而在无可删减”
  • OO语言鼓励“具有厚重的胶合和复杂层次”的体系。当问题域真的很复杂、确实需要大量抽象时,这可能是好事,但如果编程员到头来用复杂的方法来做简单的事情——仅仅是为他们能够这样做,结果便适得其反
  • 全局变量意味着代码不能重入
  • “就我个人而言,如果局部变量太多,我倾向于拆分子程序。另一个方法是看代码行是否存在(太多)缩进。我几乎从来不看代码长度。——Ken Thompson”
  • 如果通过电话向另一个程序员描述说不清楚,API可能就是太复杂,设计太糟糕了。

文本化

  • 序列化有时被称为列集(marshaling),其反向操作(载入)过程称为散集(unmarshaling)
  • 互用性、透明性、可扩展性和经济性都是设计文本格式和应用协议需要考虑的问题
  • 设计一个文本协议往往可以微系统的未来省不少力气;使用二进制协议的唯一正当理由是:如果要处理大批量的数据集,因而确实关注能否在介质上获得最大位密度,或是关心数据转化时的时间或指令开销。大图像和多媒体数据的格式有时可以算是前者的例子,对延时有严格要求的网络协议有则算作后者的例子
  • 文本格式的位密度未必一定比二进制格式低多少;设计紧凑二进制格式的思路往往不能够兼顾干净扩展的要求
  • 数据文件元格式是一套句法和词法约定,已经正式标准化或者通过实践得到充分确定
    • DSV:冒号是默认的分隔符
    • RFC 822:字段名不得包含空格,通常用横线代替,空格和tab作为当前逻辑行的延续
    • XML:需要文档类型定义(如XHTML)和相关应用逻辑赋予其语义。通常可以语法检查就能发现形式问题或数据错误
    • Unix文本文件约定
      • 如果可能,以新行符结束的每一行只存一个记录
      • 每行建议少于80字符
      • #开始注释
      • 支持反斜线\
      • 用冒号或连续空白作为字段分隔符
      • 在节格式中,支持连续行
      • 要么包含一个版本号,要么将格式设计成相互独立的自描述字节块
      • 不要只压缩或者二进制编码文件的一部分
  • 应用协议设计:如果应用协议是文本式的,那仅凭肉眼就能容易地分析,例:SMTP、POP3、IMAP
  • 应用协议元格式:应用协议元格式是为了简化网络间事务处理的序列化操作而发展出来的,因为网络带宽要比存储昂贵得多,所以需要重视事务处理的经济性
  • 目前还没有个制订较完善的元协议非常适合真正的P2P应用,不像客户端-服务器应用——HTTP在这一领域的游刃有余

透明性

  • 如果软件系统包含的功是为了帮助人们对软件建立正确的“做什么、怎样做”的心理模型而设计,这个软件系统就是可显的
  • 用户喜欢UI中的透明性和可显性,是因为这意味着学习曲线比较平缓,而“最小立异原则”就是一个体现
  • 优雅是力量与简洁的结合。优雅的代码事半功倍;优雅的代码不仅正确,而且显然正确;优雅的代码不仅将算法传达给计算机,同时也把简洁和信心传递给阅读代码的人
  • 编写透明、可显的系统而节省的精力,将来完全可能就是自己的财富
  • 用户的注意力是宝贵的,让Unix工具正常运行的最好策略是在大部分时间里沉默
  • 真正的聪明是找到方法,可以访问部分细节,但是又不让它们太显眼
  • 为透明性和可显性而设计
    • 不要在具体操作的代码上叠放太多的抽象层
    • 透明性和可显性同模块性一样,主要是设计的特性而不是代码的特性
      • 程序调用层级最大深度是多少?
      • 代码是否有强大而明显的不变性质
      • API的各函数调用是否正交
      • 程序的数据结构或分类和它们代表的外部实体间,是否有一一映射关系
      • 有多少魔法数字
    • 隐藏细节和无法访问细节有着重要区别
    • 透明的系统更容易实施恢复措施,首先就是更能抵抗bug的破坏
  • Unix程序员的品性:“宁愿抛弃、重建代码也不远修补蹩脚的代码”
  • 选择简单的算法

多路程序控制

  • Unix最具特点的程序模块化技法就是将大型程序分解成多个合作进程
  • Unix的设计风格强调用定义良好的进程间通信或共享文件来联通小型进程。因此,Unix操作系统提倡把程序分解成更简单的子进程,并专注考虑它们之间的接口
    • 降低进程生成的开销
    • 提供方法(shellout、IO重定向、管道、消息传递、套接字)简化进程通信
    • 提倡使用简单透明的文本数据格式来通信
  • 真正的难题不在协议语法而是协议逻辑——协议必须既有充分的表达能力又有防范死锁的能力
  • Unix的IPC分类
    • 最简单的形式:调用另一个程序来完成任务;专门程序通常借由文件系统和父进程通信
    • 管道、重定向和过滤器:过滤器即从标准输入顺序读数据,然后向标准输出写数据;管道操作把程序的标准输出连接到另一个程序的标准输入
    • 包装器:包装器或者将调用程序专用化,或者为它创建新的接口
    • 从进程:子程序通过连接到标准输入和标准输出的管道,交互地和调用程序收发数据
    • 对等进程通信:需要对等的通道
      • 临时文件:最古老的的IPC技法,灵活但有风险
      • 信号:每个信号都对机收进程产生默认作用,进程可以声明信号处理程序,让信号处理程序覆盖信号的默认行为
        • SIGHUP,重新初始化
        • SIGTERM,温和的终止
        • SIGKILL,立即杀死进程
      • 套接字:套接字类似文件描述符,创建时可以指定协议族来告诉网络层如何解释套接字名称
      • 共享内存:共享内存通常依靠mmap,把文件映射成可以被多个进程共享的内存

微型语言

  • 程序员每百行代码出错率和所使用的编程语言在很大程度上无关
  • 有两个好方法和一个坏方法做好微型语言的设计
    • 预先认识到可以使用微型语言设计把变成问题的规格说明提升一个层次
    • 注意到规格说明文件格式越来越像微型语言——规格中蕴含着行为
    • 错误的方法是通过扩展变成微型语言
  • 微型语言的范畴从声明性发展到命令性,从而逐渐具有通用性,当他们明确为完备图灵机时,它们就是解释器
  • 样例
    • SNG,PNG的纯文本表达
    • 正则表达式
    • Glade,描述GUI界面的XML文件
    • m4,一套宏指令集,规定文本串扩展成其他文本串的方式
    • XSLT,描述XML数据的变换
    • awk,将文本输入变换成文本输出
    • PostScript,向成像设备描述排班文本和图片的微型语言
    • bc、dc,任意精度计算
    • Emacs Lisp
    • JavaScript
  • 设计微型语言
    • 控制复杂度,声明性微型语言应该具有一个明确、一直、类自然语言的语法被人类所阅读
    • 扩展和嵌入脚本语言,实现命令性语言
    • 编写自定义语法
    • 慎用宏

生成

  • 人类其实更善于肉眼观察数据而不是推导控制流程
  • 数据比程序逻辑更易驾驭
  • 数据驱动编程:把代码和代码作用的数据结构分清楚,始终把问题层次往上推,尽量把程序逻辑转移到数据中
  • 专用代码的生成:尽可能少干活,让数据塑造代码,依靠工具,分离机制和策略

配置

  • 无论何时想增加配置选项,最好考虑下下面的问题
    • 能省掉这个功能么
    • 能否有无伤大雅的方式改变程序常规行为
    • 选项是否过于花哨
    • 需不需要一个独立的额外程序
  • Unix的程序配置信息一般在以下5个位置
    • /etc下的运行控制文件
    • 系统设置的环境变量
    • 用户主目录下的运行控制文件(通常用.开头)
    • 用户设置的环境变量
    • 启动程序命令行参数
  • 可执行未见后面加rc表示“运行控制”(命名来自CTSS的runcom命令脚本功能)
  • 一些最为常见的系统环境变量:USERLOGNAMEHOMELINESSHELLPATH
  • 常见的从-a-z的命令行选项的可能含义
    • -a,所有、添加
    • -b,缓冲区、批处理
    • -c,命令、检查
    • -d,调试、删除
    • -D,定义
    • -e,执行、编辑
    • -f,文件、强制执行
    • -g,全局
    • -h,头部、帮助
    • -i,初始化、交互式
    • -k,保留、杀死
    • -l,列表、登录、加载
    • -m,消息、邮件
    • -n,数字、否
    • -o,输出
    • -p,端口
    • -q,安静
    • -r,递归
    • -s,缄默,大小
    • -t,标记
    • -u,用户
    • -v,冗长
    • -V,版本
    • -w,宽度
    • -y,是
    • -z,启用压缩

接口设计

  • 从最小立异原则出发,启动后程序通常从下列来源获得输入或命令
    • 程序标准输入端的数据和命令
    • 通过IPC的输入
    • 已知位置的文件和设备
  • 最小立异原则不应被理解为在设计中号召机械的保守主义,新颖性提高了用户与接口最初几次的交互成本,但是糟糕的设计永远使接口令人痛苦而多余
  • “我们提倡以共生和委派策略来提高代码的复用并降低软件复杂度”
  • 最小立异原则目的就是为了减少用户在使用接口时必须学习的复杂过程
  • Unix接口设计历史:CLI => X
  • 接口的5种度量标准:简洁、表现力、易用、透明和脚本化能力
    • 简洁:事务处理需要的时间和复杂度需要有上限
    • 表现力:接口可以触发广泛的行为
    • 易用性:接口要求用户记忆的东西较少
    • 透明度:用户使用接口时,几乎不用记忆什么问题、数据或者程序状态
    • 脚本能力:容易被其他程序使用
  • CLI和可视化接口的对比
    • CLI更具表达力、脚本化能力、简洁性,适用于举例:SQL
    • 可视化接口透明度、易用性较好,适用性举例:画图、网页浏览器
    • 随着用户越来越熟练,对CLI接口的抵触也越少
  • Unix接口设计模式
    • 过滤器:接受输入,转换成其他格式,再输出到标准输出端;宽进严出、不丢弃不需要的信息、不增加无用数据
    • cantrip(没有输入输出)、源模式(无输入且输出在启动条件中控制)、接收器模式(接收输入但不发送东西到输出)、编译器模式(无标准输入输出,但会发送信息到标准错误端)
    • ed模式(编辑器模式)
    • roguelike模式(来自BSD的地牢探险游戏rogue,用字符阵列显示界面UI),如vi、emacs,没有鼠标参与,适合指法熟练的人
    • 引擎和接口分离,又或者模型和视图分离,了解MVC模式的人自然了解
      • 配置者、执行者
      • 假脱机、守护进程
      • 驱动、引擎
      • 客户端、服务端
    • 基于语言的接口模式
  • 浏览器作为通用前端
  • 如果程序没有什么有趣的或者惊奇的东西要说就应该闭嘴(有点意思)

优化

  • Unix的经验告诉我们最主要的就是如何知道何时不去优化
  • 最强大的优化技术就是不去优化
  • 先估量,后优化,直觉是糟糕的向导
  • 最有效的代码优化方法是保持代码短小简单
  • 核心数据结构必须留在最快的缓存
  • 吞吐量和延迟时间的权衡是普遍现象,例TCP、UDP
  • 对于减少延迟来说,阻塞或等待中间结果都是致命的
  • 按需计算出昂贵的结果,再缓存起来之后使用,可以兼得低延迟高吞吐

复杂度

  • 简单即美即雅即善,而复杂即丑即怪即恶
  • 程序员为了理解一个程序,会建立思维模型并调试之;程序的复杂度即模型建立和程序调试的困难程度
  • Unix思想的一个主题就是工具小巧锐利,设计从零开始,接口简单一致
  • 偶然复杂度的产生是因为没有找到实现规定功能集合的最简方法,可以通过良好设计去除;选择复杂度和期望的功能相关联,只能通过修改工程目标解决
  • 计算资源以及人类的思考,同财富一样,不是靠储藏而是靠消费来证明价值的
  • 选择需要管理的上下文环境,并且按照边界所允许的最小化方式构建程序

Implementation

语言

  • C和C++以增加实现时间和(特别是)调试时间为代价来优化效率
  • C的内存管理是复杂性和错误的渊薮
  • C语言最佳之处是资源效率和接近机器语言,糟糕的地方是槟城简直是资源管理的炼狱
  • C++试图满足所有人的所有要求
  • Perl是增强版的shell,它为替代awk而设计,适合大量使用正则表达式的地方
  • Java的设计目标是“write once, run anywhere”,但它并没有实现这两个最初的设计目标
  • Java对小项目是大材小用
  • Emacs Lisp传统上只用于为Emacs编辑器编写本身的控制程序

重用

  • 重新发明轮子之所以糟糕不仅因为浪费时间,还因为它浪费的时间往往是平方级
  • 避免重新发明轮子的有效方法就是借用别人的设计和实现,即重用代码
  • 文档并不能传达代码具现的所有细微差别之处
  • 开放源码和代码重用的关系,许多地方很像浪漫爱情和有性生殖的关系
  • 设计最好的实践需要情感的投入;软件开发者,同其他任何类型的工匠和技师一样;他们想要成为艺术家,这并不是什么私密。他们有艺术家的动力和需求,也拥有听众的欲望
  • 开放源码是从意识形态上解决这些所有问题的优先方法
  • 发布不够格软件的作者会承受许多的社会压力来修正或撤回代码(不一定)
  • 阅读代码是为未来而投资
  • 许可证
    - 许可证能够授权代码以某种形式使用,否则在版权法之下是禁止或者需要付费的
    - 非商业使用的许可证并不等同于开源许可证

Community

可移植性

  • C语言基于早期Ken Thompson的B语言解析器,脱胎于BCPL(Basic Common Programming Language),因此这个C代表Common(通用)
  • 在IETF传统中,标准必须来自于一个可用原型实现的经验;不幸的是,这并不是标准通常发展的方式
  • 搞笑RFC大概是唯一能够立即成为RFC的提议,比如RFC 1149(IP数据报的信鸽传递),RFC 2324(超文本咖啡壶控制协议)
  • 对于具备提倡标准资格的RFC,其规格必须稳定,经过同行评审,并且已经吸引了互联网社区的极大兴趣
  • IETF标准化过程有意提倡由实践而非理论驱动的标准化过程
  • 国际化的首要动作:分离信息库(配置)和代码
  • “暗含的意思就是,成为标准的最好方法就是发布一个高质量的开源实现” —— Henry Spencer

文档

  • “一切皆HTML,所有引用都是URL”
  • 绝大多数软件的文档都是由技术人员写给可能连最小公分母都不知道的普通大众的——渊博者写给无知者
  • 编写Unix文档的最佳实践
    • 数量多不会被认为是质量高
    • 信息密度适中,少用屏幕截图
    • 没人喜欢庞大的文档,考虑提供快速的摘要

开放源码

  • 开源开发的规则
    • 源码公开
    • 尽早发布,经常发布
    • 给贡献以表扬
  • major.minor.patch,补丁号修正错误和次要功能;次版本号为兼容的新功能;主版本号为不兼容的更改
  • 发布前对文档和README进行拼写检查
  • 基于所需功能而不是平台来编写移植层(面向接口编程)
    • #ifdef#if是最后一招,这通常是思路不当、产品过度差异化,无理由‘优化’或是无用垃圾聚集的先兆” —— Doug Mcllroy
  • 选择一个编码规范(lint)
  • 常见的标准文件命名规范
    • README
    • INSTALL
    • AUTHORS
    • NEWS
    • HISTORY
    • CHANGES
    • COPYING 项目许可证条款
    • LICENSE
    • FAQ
  • 以版本号来命名目录,考虑多版本在同一系统共存
  • 在设计讨论中更广泛的参与常常是件好事,但是如果列表相对开放,迟早就会有些用户在其上询问一些初级问题
  • 开源许可证
    • MIT:授予无限权利的拷贝、使用、修改和对修改的再发布,只要在所有修改版本中保留版权和许可证条款
    • BSD:授予无限权利的拷贝、使用、修改和对修改的再发布,只要在所有修改版本中保留版权和许可证条款;同时在广告和软件包相关文档中包含致谢
    • Artistic:授予无限权利的拷贝、使用和本地修改的权利。允许在发行修改后的二进制版本,但是限制源码再发行
    • GPL、Mozilla

未来

  • 分离机制(配置)与策略(算法)成为一个明确准则
  • Unix文件仅仅是个字节大袋子,而没有其他文件属性
  • 开放源码将软件业转变为服务业

–END–

2019年写下第一篇总结的时候,也未曾想到,这会成为一种一年一度的仪式,甚至能吸引到听众。但既然开始了,最好也能坚持下去。

如果说2019充满意外的话,2020只会是加大分量。不论是喜悦还是痛苦,2020留下的印记都格外得深一些。整体来看,由于遇到了一些预料之外的事情,在自我提升上向陌生方向多走了一些,不知是好是坏。

2020回顾

内在素质上,阅读技术非技术方面的书籍完成5本,小说6本。基本完成任务。

整体还是在东看看西看看的阶段,其中《程序员思维修炼》一书对思维方式的介绍补足了自己的知识体系中方法论的部分。《软技能》一书启发了在理财方面的一些认识。

大多数的书还是看了又忘,忘了就再看看总结,然后再淡忘。这个过程中,留在知识体系里幸存的部分并不多。尤其是编程相关的书籍,还是需要靠实践做项目来巩固所看的内容。没错,说的就是《Go语言入门学习》。

个人形象上,作息时间基本达到预期计划。但由于出现了新爱好(调酒),买衣服的计划被长期搁置。工装裤和卫衣还是去年那些,果然这两样买再多都不够。体重略有下降,回到80。算是基本完成目标。发型在推平后,又往偏分的方向留了起来,和此前的侧背区别不甚大。短发在身边朋友的反馈里居然还不错。

社交上,丢失和主动放弃了一些朋友,大多是三观不合或者事业变动。在和朋友的沟通交往中,更偏向主动。不过长期如此,还挺累的。热情不用更热情了,倒是应该更加增大交友面。有试图发展亲密关系的朋友,在对象上有过努力,也同时认识到很多道理,此处略去。

生活情趣上,在骑车、拉琴、看电影上仍旧投入较多业余时间,其中骑车的设备也一直还是那辆老旧的山地车。拉琴和看电影上也没有什么投入。同时,发展了喝酒和调鸡尾酒的爱好。在冰箱中屯了很多,对基酒和调酒也有了些认识。骑行上,因为圣僧的工作变动,离开了北京,一位年轻的同事成为了新的骑友。百里画廊未能成行,但是青海湖成功成行,也是一次难忘的经历。旅行上,疫情原因日本没去成,年末去了广州,吃了好吃的一本满足。拉琴上,一直也有练习,有能熟练演奏的曲目,不过都还需要看谱演奏。另外,也在公司内做过相关分享。新爱好上,看了第一次话剧,livehouse一直没遇到喜欢的乐队,未能成行。另外因为厨房卫生问题,在家做饭频率下降,反而提高了探店的频率。整体看,生活还蛮有趣味。

工作事业上,发展尚可,背靠还不错的项目组,也遇到了很多挑战,有了带小方向的机会,在团队建设上也开始有了思考。不过进步速度在下半年有所减缓,注意力有些偏移。希望在下一年能调整过来。

简单说几句

  • 积累太过理想化书面化,积累些现实点的东西,不是坏事
  • 穿衣上,裤长是最严重的问题
  • 要少喝酒,小酌小酌
  • 坚持作息和三餐,坚持骑行登山游泳轧马路
  • 要能清醒思考个人价值和现实价值
  • 一段长久的亲密关系应该是相互成就而不是相互束缚的
  • 爱合理分布在亲人、伴侣、朋友身上
  • 事业上决断力要提高,还要画饼能力,越大越好

2021展望

OK,又到了flag时间。吸取前两年的经验,目标不宜太明确,往往有意外惊喜。

  • 内在:看完囤积的4本业务书籍和10本左右小说
    • 再寻找一项和前端覆盖率类似的技术点去探索突破
    • 能清醒地评估个人价值,先知己
    • 回顾过去几年积累,完善知识体系
  • 外在:尝试新风格,保持健康,坚持运动
    • 坚持吃早饭和早睡
    • 增加锻炼频率
    • 发型尝试
    • 基本款补齐(同去年)
  • 情感
    • 平衡爱的收入和支出,更有自信
    • 能给一个人未来
  • 生活
    • 寻找一些提高生活幸福感的小东西或者经验
    • 保持室内干净卫生
    • 必要的旅行
    • 固定的户外长途骑行
    • 尝试新的菜式
    • 坚持拉琴、电影、探店
    • 至少一次livehouse
  • 事业
    • 思考职业规划和发展路线
    • 考虑落脚城市、医疗、教育等现实因素
    • 也去考虑父母的未来生活质量

就这么多吧,流水账短点为妙。诸位明年见~

Bye~

随便写写。旅程中的大多数美好,事后回忆,原来都在造化中。

序:准备阶段

这次旅行本该在去年国庆成行,由于假期有限等种种原因delay到今年这个长假。又碰巧赶上疫情和中秋带来的额外假期,于是7、8月便早早和档期向来紧张的圣僧约好。圣僧和我本是隔壁宿舍关系,从3年多前的一次结伴骑行结缘,是我的长期骑友。他是个旅行达人(友链一波:肥叉烧),身在外企,有钱有闲,经常出游,经验丰富。正巧这次碰上疫情,国外无处可去,和我定好日程后,三下五除二便把攻略搞定。和他一起出游真是省心。

Day 1:银川

出于下面多个因素考虑(其实主要是第三项),我们第一天先出发去银川。

  • 假期时间充裕
  • 慢慢适应高海拔
  • 中转机票更便宜
  • 凭借圣僧的会员,中转免费送一晚住宿外加去市区的大巴券

因为这次是从大兴机场出发,还是8点半的飞机,早上5点就得起床。

清晨的知春路口

我们在机场值机柜台碰面,托运完行李时间已经不早了,于是直接就安检进站。安检中还没收了我的修车工具(😢)。这也是这一路意外离开我的第一样东西。突然到我都没拍张照给它留个纪念。

进站后,借助圣僧招行信用卡会员,一起在VIP休息厅吃了个早餐,条件还不错。

VIP休息厅视角

9月26号的银川天气不错,我们住宿的酒店——西港航空饭店距离银川河东机场非常近。酒店建筑整体很新,周围被各种绿化围绕,完全感觉不到在西北。另外房间很大,由于远离市区,环境也很安静。加上是中转免费送的,算是我们整个旅程中最满意的住宿体验了。

西港-1

西港-2

在房间里稍作休息,我们步行到机场坐大巴前往市区,大巴券25元一位,我们使用的中转服务的免费大巴券直接乘车。大约1小时车程,我们在终点站鼓楼下车便进入了市中心。

鼓楼

稍微逛逛,我和圣僧很快就发现了多不胜数的蜜雪冰城,几乎不见其他知名品牌,剩下的也都是没听过名字的小牌子。在竞争上,蜜雪冰城比上有其很能打的性价比,比下又有标准化保证。4元的柠檬水、5元的奶茶、2元的圆筒冰淇淋。天哪,这也太香了。蜜雪冰城也将成为我们后面旅程中的一大精神寄托,按下不表。

蜜雪冰城

作为回族自治区的省会,菜单中自然没有猪肉,取而代之的是各种羊肉。从鼓楼步行街向西走过几个路口,我们惯例性在地在邮局寄了明信片。随后在旁边的迎宾楼吃了午饭,尝了下号称特色的手抓羊肉。这么一盘98元,没想象中好吃,吃了三分之一就有点腻了。最后打包给圣僧当了晚餐。

手抓羊肉

吃饱喝足,下个目标是带些伴手礼。宁夏的枸杞是比较出名的,其中以中卫的为最优。枸杞分红枸杞和黑枸杞,都没有明确的药用价值。但是日常保健还是有用的,号称能补气虚,很适合阳气虚弱的男性。黑枸杞据说劲儿更野一点。稍微做了下功课,我们在超市一人来了2斤,一斤75元。事后看,这个价格似乎有些上当,建议各位小买怡情即可。

银川城区内的共享单车似乎都是电动车,不论是青桔还是小蓝,还是美团单车。在一天的观察下,只注意到凤毛麟角的人力单车。这到底是因为当地人自行车技术很烂还是电动车驾驶技术很普及?挺有意思。

共享单车

天色渐暗,在人民公园感受了城市氛围后,我们乘坐大巴回到西港航空饭店,为第二天前往西宁做准备。

Day 2: 西宁

西宁相比银川更有省会的感觉。不同于银川的回族居多,西宁有着比较明显的藏族气息,藏民也很常见。高海拔的强烈紫外线下,大多数人都是黝红的脸。由于第二天要坐大巴前往西海镇,我们这一天住在汽车站旁边的如家,在市区中处于偏东郊的位置。不出意外,楼下又是一家蜜雪冰城。

酒店

西宁城区内有湟水自西向东流过,我们下午先是打车到了河边的中心广场,类似北京的元大都城垣遗址一样,不过明显更大且更有层次,湟水河宽度近似海河。在河边坐也好,走也好,感觉都是很不错的。下午3点,气温十余度,在河边的长椅上,一直都能看到飞机在高楼的天际线上划过。

西宁海拔约2100左右,处于多个山脉中的河谷地区,适宜建设城市的面积不大,只有大概一个十字形状的区域。所以城区中的楼房都格外的高,且能看出一些地势起伏。整体感觉,似乎来到了小号版的重庆。和重庆一样,这里看不到共享单车。

城市景观

不过从一些城市的角落,还是能瞥见藏在深处的旧工业时代的厂房气息。

旧厂房

我比较偏好用随便走走四处看看的方式感受城市的风格。下午我们从中心广场绕过力盟商业区,再穿过九龙城寨般的商业巷大世界,从人民公园门口向回走,最后又回到颇具人气的力盟步行街。不能免俗,吃了顿呷哺呷哺。

呷哺呷哺

Day 3: 西海镇 - 种羊场

里程:48km

去西海镇的车票我们是提前一天买好的,25元。但因为游客不多,其实也可以当天早上买。从火车站背后上京藏高速,一路穿过湟中区、湟源县、海晏县就到达了最终骑行的出发点西海镇,全程约2小时。租车上,我们早已提前和当地租车师傅约好,6天行程,租车500元,外加解决住宿700元(对租车店老板安排的住宿不要抱太高期望)和押金500元(结束骑行后退还),一共1700元。

西宁到西海镇

西海镇是海北藏族自治州的州首府,不过却意外的荒凉,街道两边政府部门倒是周正齐全,基础设施也是一应完善,可是就是人迹稀少,连车都没几辆。我们下车后已经10点,第一天尚有40多公里的路程要骑。我们调试好车辆,装好驮包,穿戴好装备,擦好防晒(这个十分重要),简单吃了个饭就出发了。

出发合影

沿着刚察路直走,穿过和G213国道的路口,就上了环湖东路。沿着环湖东路直走就可以达到第一天的目的地——湖东种羊场。路线也是相当简单。

路牌

初见丘陵和草原,配合着壮观的云层,还是很有新鲜感的。

草原1

草原2

山丘1

第一天的路并不像路书中那样全是平路,相反上下坡并不少。幸运的是,路边的景观稍微减缓了骑行的疲惫感。

上坡1

上坡2

穿过和G213国道的分岔路口后,我们离湖越来越近了。

996

路牌

随着湖水越来越近,金银潭和沙岛上起伏的沙丘却是也越来越显眼。湖边的荒漠化土地是我预先没想到的,据说是气候原因导致。但从沿路看到的各种牛羊来推断,和过度放牧可能也有些关系。

沙漠1

牦牛1

穿过金银滩沙漠区,是一个大下坡,下坡后路两侧又逐渐恢复了植被和河流,看起来顺眼多了。

大下坡

绿地

青海湖边地广人稀(整个青海除了西宁和海东应该都是地广人稀),除了风声,路两旁十分宁静,若是没有毒辣的紫外线,实在惬意极了。

车1

再往前骑数公里,终于到达种羊场。这并不是一个镇或县,顶多是围绕湖东种羊场的一个聚落。住宿环境自然十分感人。卫生糟糕,门窗都可以从外面打开。具老板所说,这里民风淳朴,姑且只能信了。

种羊场住宿

幸好有炕锅羊肉和壮观的落日场景可以抚慰。

炕锅羊肉

落日1

落日2

青海湖周围海拔3200到3400,昼夜温差较大,太阳一落山,气温就比较冷了。种羊场也无甚可逛,我们直接倒头就睡,洗漱更衣直接放弃。只期待第二天住宿能好点。

Day 4: 种羊场 - 江西沟

里程:55km

第二天一早我们吃了点买好的干粮,擦了防晒就直接出发。种羊场实在没什么好留恋。青海湖环湖的路都很直,两侧要么是无边的草原,要么是起伏的山丘,要么是看不到头的湖面。以至于路也看不到终点,似乎永远都骑不到头。

路1

不过在骑行中,能察觉到远处的村庄、山岭、景观一点点靠近,还是蛮奇妙。

路2

路边是牧民们的牛羊,似乎和内地的品种并不相同。牦牛们都很沉默,羊们也是,我蹩脚地起个头,它们也只是懒懒地抬个头望向我,依旧沉默。马儿们有的自由吃草,被人看管的则用来招徕游客骑行创收。

牛

羊

骑过错果,便告别了环湖东路S207,来到G109国道,相比环湖东路,国道上的车辆明显多了很多。

错果

路牌2

从环湖东路和G109的交叉口到二郎剑景区,靠湖一侧都有维护良好的辅路,路面起伏不大,还有成片的油菜花可以欣赏,油菜花都不高,游客在其中拍照能露出脑袋,从车上看颇有趣味。

油菜花

路的另一侧是连绵的不知疲倦的山丘,它将一路陪伴我们告别G109国道。

山丘2

临近二郎剑景区有可以直接接近湖面的地方。这是一路第一次能亲近湖水的机会。

青海湖

在二郎剑边的中石油稍作休息,是一路枯燥的景观,16km后便到达终点江西沟。令人欣慰的是,住宿环境有了些微的改善。

江西沟住宿

Day 5: 江西沟 - 黑马河/茶卡

里程:48km

这一天的里程虽然不长,却是环湖几天中,最疲惫的。原因是没有止境的逆风。一路上,景观和前一天无异,枯燥的山丘和草原,连湖面也不太能看见了。因为恼火的逆风,我甚至都没心情抬头看看路两旁的风景。

也许是因为大风把碎云都卷走,天上的云倒是挺好看。

云1

云2

我大约提前圣僧1个小时到达,考虑到高原和糟糕的风向,圣僧临时修改计划,将第二天的茶卡盐湖之旅提前,把本来一天到达刚察县的计划拆成石乃亥 - 刚察县。这一决定不仅让我们看到了茶卡盐湖上的落日,也极大提升了后两天的骑行体验。

黑马河

黑马河位于G109和环湖西路S209的交叉口,前往茶卡、鸟岛,大西北自驾环线、环湖骑行都要经过这里。本来是发展不错的一个小镇。近些年考虑到环保因素,拆了大多数建筑,统一由政府管理建设,道路管道也大兴土木整修,目前看起来完全感受不到当地人所说当初的繁华。

我们运气不错,碰巧民宿老板要前往茶卡办公事,在圣僧下午3点到达后,我们搭了个便车去往茶卡(来回两人300元)。走G109去茶卡镇要穿越橡皮山,最高海拔3800m,且单程80km。对于骑行来说不太现实。老板比较健谈,一路和我们介绍了青海湖的旅游发展以及周边地理人文,挺有意思。

橡皮山1

橡皮山2

茶卡盐湖的风景十分看天气。晴天无风的天气下,才有天空之镜的感觉。我们去的那天虽然晴空万里,不过风力较大,差不多夕阳落山时,西风终于转弱,得以让我拍到下面的景象。

夕阳3

夕阳4

盐湖门票原价60,对青海、湖北、浙江籍游客免票。小火车单程50,车速和步行差不多,没有强需求可以不做。小火车沿着一条向湖区深处的路一直开到最里面,大约30分钟。路的西边湖水较浅,许多已蒸干出盐巴,还能看到湖盐厂的旧铁路和新厂址。许多游客穿着租赁的胶鞋在水面嬉戏。

盐湖游客

搭配雪一样的湖盐,有种北极村的感觉。

"北极村"

坐车回到黑马河时已经晚上9点了,晚饭是牦牛肉串 + 牛肉面,巴适得很。

肉串加牛肉面

Day 6: 黑马河 - 石乃亥

里程:39km

没有了恼人的逆风后,这一天好骑多了(再加上里程短,应该是环湖最轻松的一天)。骑出黑马河镇,就和G109国道告别,上了环湖西路,车辆明显稍多了,整体体验大大提升。经过一段笔直的路程,可以绕到离湖不远的地方,接下来的一路也都如此,还能时不时看到自驾游的游客在路边拍照。

湖边

等接近石乃亥后,我们便离湖越来越远。经过一个小上坡,便能远远看到石乃亥乡的轮廓了。

小上坡1

小上坡2

石乃亥不大,基础设施倒是挺完善。由于到得比较早,我们很轻松吃了个土火锅作为午饭。土火锅里面有牦牛肉和各种素菜,价格145,还算划算。我们大快朵颐了快1个小时,最后也没吃完。

土火锅

Day 7: 石乃亥 - 刚察县

里程:78km

骑出石乃亥约20公里,我们告别了海南共和县,来到海北刚察县境内。一如往常又是广袤的草原和起伏的山丘。骑过关闭的鸟岛,就来到了湖边的最后一段路。

湖羊

站在环湖东路的末尾处回头望,能看到最后一片湖面的波光粼粼。

波光粼粼

从岔路口右拐上G315国道,会贴着青藏铁路走一段。之后越过新修的西和高速便进入县城内。

岔道

铁路1

铁路2

刚察县虽然只是个县城,但是热闹程度和城市美化程度一点不亚于西海镇,甚至更甚。

刚察县

县城里的藏城电影院也在营业,虽然放映厅很小,还没有大学大教室大,但两人包场看《我和我的故乡》的体验还不错。

刚察电影院

吃厌了羊肉,我们晚饭在一家成都冒菜馆解决了晚饭,能有吃菜自由的感觉真好。

冒菜

另外,刚察的旅游厕所做的是真不错,体验比北京的管理公厕都要好。

刚厕

Day 8:刚察县 - 西海镇

里程:90km

最后一天离湖就很远了。出刚察县先是一个大上坡,接着几乎一路平路骑28km可以到达哈尔盖河边的哈尔盖镇。

刚察县上坡顶

在哈尔盖中石化稍作休整,沿着315国道再骑20km到达甘子河乡,开始最后一个长坡,从海拔3200m来到3415m。

哈尔盖车站路口

G315-170km

从这个坡顶一个俯冲下来穿过西和高速,便从G315回到G213国道。这一段逆风很大,由于修路,大车也很多。它们开过的同时,尘土飞扬,恨不得把自行车都给吸进去。

G315下坡

在一段艰辛的骑行后,我们来到修路的终点,也是整段路的最高海拔3445m,接着一路下坡,20km后在G213的拐弯处,西海镇已经越来越清晰。

骑行结尾

我们最后还了车,又寄了明信片。在西海镇停留了最后一天。这里作为海北藏族自治州首府,各种政府部门和基础设施完备齐全。就是人迹稀少。偌大的跳广场舞的活动中心也没个人影。

海北藏族自治州文化活动中心

夜晚了,路边的住房都亮起灯,仿佛又回到当初两弹一星时期建立原子城的岁月。

原子城夜景

Day 9/10: 西海镇 - 西宁 - 北京

最后一天,我们回到西宁。重回城市的感觉真好,标准化和工业化给人太饱满的安全感了,躺在床上,回想起这为期6天的骑行,尽管风景很原生态很美好,但这也是以牺牲了工业化和标准化为代价的。除开住宿环境,缺少便捷的物流和交通,让沿途小镇的餐饮费物价居然比在西宁高。离开西宁前往西海镇前,我们恐怕没有预期到,以前毫不在意的蜜雪冰城和德克士居然成为了一种奢求和精神寄托。也许,人们都是图个新鲜,享受惯了工业化城市化带来的福利,居然也会想吃吃贫瘠和原生态的苦。

经过此次“艰苦”的骑行,圣僧说他心心念念的川藏线骑行也要好好考虑考虑了,可能磨练人的不是骑行本身,反而是沿途令人生畏的住宿环境。我们脸上和腿上的晒伤需要时间痊愈。不知道,圣僧心理上的畏惧又需要多久痊愈。

–END–

MF的《重构》一书算是程序设计书籍的经典了。其中对于重构的认识和剖析深入浅出,提纲挈领。对于有一定编程经验的人来说更是如虎添翼的帮助。下面我尽量在不贬损原意的基础上,用自己的思路和语言进行适当的总结。

序 & 前言:重构的再认识

开篇名义,还未进入正文,书从序和前言开始,便不自觉间流露着真知灼见:

  • 重构是不改变软件可观察行为的前提下改善其内部结构
  • 重构需要你维护一份“坏味道”和重构手段的对应
  • 设计前期使用模式通常会导致过度工程
  • 代码总将随着设计者的经验成长而进化

样例:感受重构

任何一个傻瓜都能写出计算机理解的代码。但唯有优秀的程序员才能写出人类能理解的代码

代码被阅读和修改的次数远多于被编写的次数。尽管代码在机器中运行时,机器并不会嫌弃代码丑陋。但是代码总是要修改的,当我们打算修改系统时,就涉及到了人。人在乎这些。差劲的系统很难维护,如果很难找到修改点,程序员就可能犯错,从而引入bug。如果你发现你需要为程序增加特性,但是当前的代码结构让你不能方便达成目标时,先重构那个程序,再方便地添加特性。

当然,重构前一定要确认,自己有没有一套可靠的测试机制,因为你需要它来保证重构的基础要素:不修改已有功能。重构中,最好能以微小的步伐前进(这样能及时回滚)。在本章样例的重构中,体现了下面一些“好味道”:

  • 代码块越小,代码功能就越好管理
  • 好的代码应该能够清楚表达自己的功能,变量名也是代码清晰的关键
  • 用多态取代条件判断逻辑
  • 结构化风格相比过程化风格更易扩展也更好维护

原则

本章介绍了重构的一些原则和基础性认识。

  • 何为重构:不改变软件可观察特性的前提下,通过修改内部结构,提高其可理解性。通常情况下和性能优化相对应
    • 两顶帽子:添加新功能和重构应该属于两种截然不同的行为,它们应该分开交替进行
  • 重构的好处
    • 改进软件设计,整理代码让后续的修改更容易
    • 让软件更好理解,准确说出我想要的
    • 帮忙找到bug
    • 提高未来的编程速度
  • 何时重构
    • 事不过三,第一次只管去做,第二次产生反感但还是去做,第三次做类似的事情就去重构
    • 修改bug时重构
    • review代码时重构
  • 间接层和重构:中间层能够允许逻辑共享和意图的分开解释,同时隔离变化和解耦。
    • 提前设计好中间层不如先直接做再重构
    • 少数情况下,中间层只会带来冗余
  • 重构的难题
    • 修改已有API:建议维护新旧两个接口,让用户做出反应后,再迁移。这期间,旧接口应该要调用新接口实现
    • 代码已经无法正常运行时,重写比重构更省事
  • 重构和性能优化:大多数的性能优化集中在小部分代码上。先写出风格良好的代码,再使用性能工具实测数据,对瓶颈处单独优化性能。好的重构也会让性能优化更容易进行

坏味道

在遇到下面一些“味道”时,可能你就需要重构了。

  • 重复代码
  • 函数过长,每当你需要用注释说明点什么时,可以把需要说明的东西写到一个独立函数中
  • 太长的类
  • 函数入参过多
  • 发散式变化:一个类因为多个原因发生不同的变化
  • 霰弹式变化:一个原因引起一个类的多个变化
  • 特性依恋:函数对某个类的兴趣高于自己所在的类
  • 数据泥团:喜欢聚合在一起的零散数据字段
  • 基础类型偏执:对于基础类型如字符串、整型不愿意使用简单类来封装
  • swtich语句
  • 冗余类
  • 夸夸其谈未来性:过度为未来设计
  • 令人迷惑的暂时字段
  • 过度耦合的链式调用,如a.b.c().d(),链上任意类做修改都会影响整个调用
  • 两个类的狎昵关系
  • 异曲同工的类
  • 幼稚的数据类:只有最简单的getter和setter
  • 子类拒绝继承超类的函数或数据
  • 过多的注释

测试体系:重构的保证

前面已经提到数次,重构的前提是不对已经已有行为做改动,这需要测试的帮助。本章对建立测试给了一些简单的介绍。

  • 编写测试代码最有用时机是编程之前
  • 编写一个测试case时,可以先让测试失败,再通过成功验证程序功能
  • 遇到bug时,先添加一个单元测试复现这个bug
  • 测试不能保证程序没有bug,编写测试样例也遵循82原则,当样例已经很多时,它带来的边际效果就没那么好了。应该更多考虑容易出错的边界条件,积极思考如何“破坏代码”。

重构列表

下面分几大方向介绍具体的重构手段。每个手段会分场景、思路、动机、做法来展开。

组织函数

日常工作中,非常容易坏味道中的过长函数,下面的一些重构方式可以帮我们优化这一点。

提炼函数

  • 场景:有一段相对独立的代码可以被组织并独立出来
  • 思路:将这段代码放到一个独立函数中,用函数名解释该函数的用途
  • 动机:有时会遇到过长函数中有一段需要注释才能看明白的代码。将这样相对独立的逻辑拆分成表意的短小函数后,可以让高层函数读起来就像一系列注释。如果提炼可以提高代码清晰度,就算函数名比函数体长都无所谓
  • 做法:用做什么而不是怎么做来为函数命名(如果你想不出一个更有意义的名称,就别动了)。检查是否有临时变量,如果有读取,可以作为入参传递给函数;如果对临时变量甚至有再赋值,那可能还要让函数返回临时变量修改后的值

内联函数

  • 场景:函数本体和名称一样清晰易懂
  • 思路:在函数调用点插入函数本体,然后移除函数
  • 动机:如果函数本体足够简单,且表意清晰,同时调用点有限,不具备多态性。那么出于减少无用中间层的考虑,可以直接使用函数体
  • 做法:注意检查是否有多态性

内联临时变量

  • 场景:一个临时变量只被简单表达式赋值一次,同时妨碍了其他重构手法
  • 思路:将对变量的引用动作,替换成给它赋值的表达式本身
  • 动机:过多的临时变量会妨碍你重构长函数
  • 做法:注意确保表达式没有副作用

以查询替代临时变量

  • 场景:程序中有个临时变量保存了某个表达式的运算结果,同时被多处引用
  • 思路:将表达式提炼成独立函数,在独立变量的所有引用点替换成对新函数的调用
  • 动机:替换成函数后,整个类都可以获得这份信息,同时会减少对该变量的频繁引用带来的重构困难
  • 做法:寻找只被赋值一次的临时变量,对于赋值多次的临时变量使用“分解临时变量”方法先重构,保证提炼出来的函数没有副作用。先不要担心性能问题,等到出现了优化也会比较简单

引入解释性变量

  • 场景:有个复杂的表达式,表意不够清晰
  • 思路:将表达式的值放进一个临时变量,用变量名表意
  • 动机:表达式不如变量名更好阅读。如果临时变量在整个类都有意义,建议直接使用“提炼函数”方法
  • 做法:先判断是否使用“提炼函数”的做法

分解临时变量

  • 场景:某个临时变量被多次赋值,且每次赋值意义不一样
  • 思路:针对每次不同意义的赋值使用不一样的临时变量
  • 动机:临时变量的多义性会增大理解成本
  • 做法:寻找被多次赋值且有多义性的变量,不同的意义使用新的不同临时变量

移除对函数入参的赋值

  • 场景:对函数入参赋值
  • 思路:用新的临时变量取代入参
  • 动机:对入参赋值会混淆按值传递和按引用传递的传参方式
  • 做法:略

用函数对象取代函数

  • 场景:大型函数中代码过于复杂,无法使用“提炼函数”
  • 思路:直接将函数放在单独对象中,将复杂的局部变量变成对象字段,从而可以轻松地在对象中分解这个大型函数到多个小型函数
  • 动机:略
  • 做法
    1. 建立一个新类,用函数用途给这类命名
    2. 在新类中创建final字段保存大型函数所在的对象,即“源对象”
    3. 新类的构造函数使用原函数入参作为入参
    4. 新类中建立computed()函数
    5. 赋值原代码到computed()
    6. 在原函数位置,创建这个新类的一个对象,并调用这个对象的computed()函数
    7. 继续重构新类中的computed()函数

替换算法

  • 场景:某个算法有更清晰的算法替代
  • 思路:直接更换函数本体
  • 动机:略
  • 做法:略

对象间的特性搬移

类应该承担清晰且明确的责任。不论是承担责任过多还是“不怎么负责任”,都需要进行重构。

搬移函数

  • 场景:有函数和所在类以外的其他类反而有更多交流,如调用或被调用
  • 思路:在和函数交流更多的类中建立一个有类似行为的新函数,改造旧函数为新函数的委托函数,或者直接移除旧函数
  • 动机:略
  • 做法:
    • 检查和搬移函数关联的字段或函数,判断是否要一起搬移
    • 检查子类和超类有无其他声明,检查有无多态性
    • 如果目标函数需要太多源类特性,就需要进一步分解后再搬移

搬移字段

  • 场景:某个字段和所在类以外的其他类有更多交流
  • 思路:在目标类新建字段,修改源字段的所有使用者,令它们使用新字段
  • 动机:略
  • 做法:如果字段的访问级别是public,需要先用“封装字段”手段制造一个委托中间层

提炼类

  • 场景:某个类做了两个类的事情
  • 思路:建立新类,搬移函数和字段
  • 动机:一个类应该是清楚的抽象,即可以使用清晰的命名
  • 做法:拆分类,建立两个类之间的单向或双向连接,搬移底层函数,搬移高层函数

内联化类

  • 场景:某个类没做什么事情
  • 思路:将这个类的特性搬移到其他类,然后移除原类
  • 动机:通常会由于此前的重构动作移走了这个类的责任
  • 做法:选择和这个类关系最近的类进行合并,可以先在目标类中使用委托,然后再通过搬移函数的方式完成重构

隐藏委托关系

  • 场景:使用者通过委托类来调用对象
  • 思路:在提供服务的类上直接建立使用者所需的所有函数,隐藏委托关系
  • 动机:隐藏调用关系可以减少实现细节暴露从而减少耦合
  • 做法:在发起请求的类中,直接实现功能的接口,移除使用者的委托代码

移除中间人

  • 场景:类做了过多简单委托的动作
  • 思路:让使用者直接调用受托类
  • 动机:当“隐藏委托关系”使用过多时,封装会很痛苦,这个时候不如直接让使用者通过链式调用用中间受托类实现功能
  • 做法:刚好是“隐藏委托关系”的反向过程

引入外加函数

  • 场景:需要为提供服务的类新增函数,但是你无法修改这个类(通常是库代码)
  • 思路:在使用者类中建立一个函数,并用第一参数的方式传入服务类实例
  • 动机:尽管可以在不修改服务类代码的情况下,自行添加新函数,但还是建议当外加函数较多时,使用“引入本地扩展”的方式,或直接推动服务类升级
  • 做法:在客户类中建立函数,这个函数不调用客户类特性,只是转发请求到服务类

引入本地扩展

  • 场景:需要为服务类添加一些额外函数,但你无法修改这个类
  • 思路:建立一个新类,使其包含这些额外函数,让这个扩展类成为源类的子类或包装类
  • 动机:子类工作量较少,但是必须在对象创建期接管创建过程;包装类只是单纯转发请求,没有这个限制,但是转发过程都需要自己实现
  • 做法:略

重新组织数据

自封装字段

  • 场景:直接访问一个字段的方式给你的重构带来了麻烦,或是引入了麻烦的耦合关系
  • 思路:用取值/设值函数替代直接访问字段
  • 动机:这种方式让字段更为灵活,但是根据奥卡姆剃刀法则,等需要的时候再用
  • 做法:有的字段可能需要一个初始化函数

用对象取代数据值

  • 场景:数据项需要和行为合在一起使用才有价值
  • 思路:把简单的数据项封装成对象
  • 动机:开发初期的简单数据,可能在迭代后会加上特殊行为,如果不及时处理,就会出现特性依恋或重复代码
  • 做法:略

将值对象改为引用对象

  • 场景:从一个类会衍生出多个实例,实例间只是一个实体的多种状态
  • 思路:将值对象改为引用对象
  • 动机:值对象通过equals()hashCode()判断,如日期;引用对象则直接可以用相等操作符==判断,如顾客、账户等概念
  • 做法:你可能需要一个静态字段或提前创建好多个新对象作为访问点

将引用对象改为值对象

  • 场景:你的引用对象很小且不可变,同时不易管理
  • 思路:将引用对象改为值对象
  • 动机:引用对象不好控制,值对象的不可变特性在某些场景很好用。
  • 做法:只有不可变对象才能被重构

以对象取代数组

  • 场景:有个数组,其中的元素类型不一,代表不同的东西
  • 思路:用对象替代数组,用字段表示不同意义的元素
  • 动机:数组的作用是以某种顺序存储一组相似对象,不要让位置具有特殊意义
  • 做法:略

复制被监视数据

  • 场景:有些领域数据被放在了GUI部分代码里
  • 思路:将数据复制到领域对象中,建立Observer模式,剥离UI和逻辑
  • 动机:分层良好的系统,用户界面和业务逻辑代码是分开的,这样也更好维护
  • 做法:略

将单向关联改成双向关联

  • 场景:两个类都需要对方特性,但目前只有单向连接
  • 思路:增加一个反向指针,同时修改函数能够同时更新两条链接
  • 动机:略
  • 做法:注意删除过程移除指针的顺序

将双向关联改为单向关联

  • 场景:两个类有双向关联,但是一个类已经不需要另一个类的特性
  • 思路:去除不必要连接
  • 动机:维护双向连接带来便利的同时,也会增加维护的复杂度
  • 做法:略

用常量取代魔法数

  • 场景:有个字面量数值,具有特殊含义,但是不能一眼看明白
  • 思路:创造一个常量,用命名说明字面数值的意义
  • 动机:魔法数是类型码时,要使用“以类取代类型码”
  • 做法:略

封装字段

  • 场景:类中有public字段
  • 思路:声明改为private,提供相应的访问函数
  • 动机:暴露public会降低函数的模块化程度,数据应该和行为集中在一起,不应被直接修改
  • 做法:略

封装集合

  • 场景:函数返回一个集合
  • 思路:返回集合的只读副本,并在类中提供添加/移除集合元素的函数
  • 动机:类似“封装字段”,返回的集合一样可能被修改
  • 做法:使用Collection,或返回一个副本

用数据类取代记录

  • 场景:面对传统编程中的记录结构
  • 思路:创建“哑”数据对象
  • 动机:要将记录型结构转成面向对象的程序中
  • 做法:创建private字段,创建读写函数并提供

以类取代类型码

  • 场景:类中有个数值类型码,但是不影响类行为
  • 思路:用新的类替换数值类型码
  • 动机:略
  • 做法:略

以子类取代类型码

  • 场景:类中有个不可变数值类型码,同时影响类行为
  • 思路:用宿主的子类替换类型码
  • 动机:可以用子类的多态性取代switch语句,不过,如果类型码会发生改变,或者宿主类已经有子类则不能用此方法
  • 做法:略

以状态/策略取代类型码

  • 场景:类中有个数值类型码,会影响类行为,同时不能通过继承来消除
  • 思路:以状态对象取代替换数值类型码
  • 动机:略
  • 做法:创建一个新的类,用类型码的用途为它命名,这就是一个状态对象。所有的新类继承自超类,返回不同的状态码

以字段取代字段

  • 场景:子类的查边只在返回常量数据的函数上
  • 思路:修改函数,让它们返回超类的新增字段,然后销毁子类
  • 动机:这样可以避免继承带来的额外复杂性
  • 做法:略

简化条件表达式

条件逻辑会增加理解的层级,处理不好时,很容易配合长代码造成理解困难。

分解条件表达式

  • 场景:有一个复杂的条件语句
  • 思路:为if、then、else语句段落提炼独立函数
  • 动机:条件逻辑通常会使代码更难阅读
  • 做法:使用表意的函数名说明条件语句意思

合并条件表达式

  • 场景:有一系列的条件逻辑,都得到相同结果
  • 思路:合并成一个条件表达式,并将之提炼成一个独立函数
  • 动机:有时候这么做能把“做什么”的语句转换成“为什么”的含义,前提是这些检查并非彼此独立
  • 做法:注意确认条件语句都没有副作用,有些条件表达式甚至可以简化成三元表达式

合并重复的条件片段

  • 场景:条件表达式的每个分支都有相同的一段代码
  • 思路:将代码提取到条件表达式之外
  • 动机:减少重复语句
  • 做法:略

移除控制标记

  • 场景:在一系列布尔表达式中,某变量具有控制标记的作用
  • 思路:用breakreturn替代
  • 动机:有时候为了可读性和可维护性,可以牺牲单一出口的做法
  • 做法:略

用“卫语句”替代嵌套条件表达式

  • 场景:嵌套的条件逻辑过多,难以看清正常执行路径
  • 思路:用“卫语句”枚举出所有特殊情况,减少嵌套层数
  • 动机:当特殊case多于正常case时,提前处理每种特殊情况,可以有效减少嵌套层数
  • 做法:注意“卫语句”要么就从函数返回,要么就抛出异常,反正要跳出当前执行流

用多态取代条件表达式

  • 场景:你手上有个条件表达式,根据对象类型不同选择不同行为
  • 思路:将条件表达式的每个分支放在子类的重载函数中,然后将父类的原始函数声明为抽象函数
  • 动机:面向对象程序中,更少出现switch语句也是得益于多态这个工具
  • 做法:略

引入Null对象

  • 场景:在很多地方检查对象是否为null
  • 思路:用一个特殊的Null对象取代null
  • 动机:空对象对外就像是特殊的空的对象(Go笑而不语),而不是什么都没有,有利于保证函数行为的一致性
  • 做法:空对象一定是单例的

引入断言

  • 场景:某段代码需要对程序状态做出假设
  • 思路:用断言表示这种假设
  • 动机:有些时候,只有某个条件为真,代码才能正常运行,这个时候用断言明确这些假设。
  • 做法:注意不要滥用断言,只用来检查“一定为真”的条件,而不要去检查“应该为真”的条件

优化函数调用

我们在前面提到了函数体本身的优化,这一章我们主要介绍函数调用的优化

函数改名

  • 场景:函数名没能说明函数用途
  • 思路:修改函数名
  • 动机:优化函数名,让它达到注释的效果,重新安排参数顺序,提高代码清晰度
  • 做法:对于旧函数,可以标注deprecated,说明其不建议使用

添加参数

  • 场景:函数需要从调用端得到更多信息
  • 思路:为函数添加新的对象参数
  • 动机:如果有其他重构的方法,只要可能,基本都比添加参数要好
  • 做法:略

移除参数

  • 场景:函数本体不需要某个参数
  • 思路:去除该参数
  • 动机:暂时不要考虑未来是否能用到
  • 做法:略

分离查询和修改

  • 场景:一个函数即返回对象状态,同时又有副作用
  • 思路:将查询和修改分离出两个参数
  • 动机:任何有返回值的函数,最好都不要有看得见的副作用
  • 做法:先分离查询,再分离修改

让函数携带参数

  • 场景:若干函数做了类似的操作,仅仅因为某些值表现不同
  • 思路:用一个单一函数表示,用参数来表示那些不同的值
  • 动机:减少重复代码
  • 做法:略

用明确函数取代参数

  • 场景:有一个函数,其中完全取决于参数表现出不同行为
  • 思路:针对参数的不同值,建立一个独立函数
  • 动机:函数内大多以条件表达式检查这些参数值,并作出不同行为;有时也可以用多态实现
  • 做法:略

保持对象完整

  • 场景:你从对象中取了若干字段,将它们作为函数调用的一些参数
  • 思路:改为传递整个对象
  • 动机:如果传递整个对象会让你的依赖结构恶化,那么就不该用这个方法
  • 做法:略

用函数取代参数

  • 场景:对象调用某个函数,用其结果做参数传递给另一个函数,然而接受改参数的函数本身也能调用到前一个函数
  • 思路:让参数接受函数直接去调用前一个函数,然后去除该参数
  • 动机:如果函数有其他途径获得参数值,就不该通过参数获得
  • 做法:略

引入参数对象

  • 场景:某些函数入参总是在一起出现
  • 思路:直接用一个对象取代这些参数
  • 动机:略
  • 做法:略

移除设值函数

  • 场景:类的某个字段在创建时设值,然后就不再改变
  • 思路:去掉字段的设值函数
  • 动机:提供设值字段就表示可能被改变
  • 做法:略

隐藏函数

  • 场景:有函数从未被其他类用到
  • 思路:将函数改为private
  • 动机:减少无谓的API暴露
  • 做法:可以利用lint工具帮忙检查

用工厂函数替代构造函数

  • 场景:希望创建对象时不仅做简单的构建动作
  • 思路:使用工厂函数
  • 动机:这个方法也可以用来通过类型码创建类对象
  • 做法:结合Class.forName()可以不用写switch语句

封装向下转型

  • 场景:函数返回的对象需要由调用者向下转型
  • 思路:将向下转型放在函数中进行
  • 动机:略
  • 做法:略

用异常取代错误码

  • 场景:函数返回特性的代码表示错误情况
  • 思路:改用异常
  • 动机:异常能够区分出正常情况和异常处理
  • 做法:需要决定抛出受控异常或者非受控异常

用测试取代异常

  • 场景:对于一个调用者可以预先检查的条件,抛出了异常
  • 思路:修改调用者,改在调用前进行检查
  • 动机:能够提前检查的情况,就不算是异常
  • 做法:略

处理继承关系

字段上移

  • 场景:两个子类有相同字段
  • 思路:将字段移至超类
  • 动机:归纳重复特性
  • 做法:略

函数上移

  • 场景:两个子类有相同作用的函数
  • 思路:将函数移至超类
  • 动机:归纳重复特性。子类的函数覆写超类函数,但是做相同工作时,也要使用函数上移
  • 做法:略

构造函数上移

  • 场景:子类的构造函数几乎完全一致
  • 思路:在超类中新建构造函数,再在子类构造函数中调用它
  • 动机:如果重构过程过于复杂,可以考虑使用“用工厂函数替代构造函数”
  • 做法:略

函数下移

  • 场景:超类的某函数只和部分子类有关
  • 思路:将函数移到相关的子类中去
  • 动机:和“函数上移”恰恰相反
  • 做法:略

字段下移

  • 场景:超类的字段只被部分子类用到
  • 思路:将字段移到真正需要的子类中去
  • 动机:和“字段上移”恰恰相反
  • 做法:略

提炼子类

  • 场景:类的特性只被部分实例对象用到
  • 思路:新建一个子类,将未被用到的特性转移到子类中
  • 动机:上述的差异行为有时也可能通过类型码区分,这个时候可以由“以子类取代类型码”或“以状态/策略取代类型码”方法来重构
  • 做法:略

提炼超类

  • 场景:两个类有相似特性
  • 思路:为两个类建立超类,将相似特性移到超类中
  • 动机:两个类用相同方式做类似事情往往意味着重复代码
  • 做法:略

提炼接口

  • 场景:若干客户端使用类中的同一子集,或者两个类有部分相同点
  • 思路:将相同的子集提炼到独立接口中
  • 动机:接口有助于系统的责任划分能力声明(鸭子类型)。在单继承的语言中,接口扮演了组合功能代码的角色。尤其某个类在不同环境表现不同时,使用接口是个好主意
  • 做法:接口命名通常由-able结尾

折叠继承关系

  • 场景:超类和子类几乎无法区分
  • 思路:将它们合为一体
  • 动机:往往在过度设计时出现
  • 做法:略

构造模板函数

  • 场景:有一些子类,细节上有所区别,但是整个流程上操作类似
  • 思路:提炼出操作流程,上移至超类,将具体细节操作放在独立函数中,让它们有相同的签名,然后实现超类的抽象函数
  • 动机:这样抽离出来的流程函数也叫模板函数,模板上插槽接口固定,然而提供插槽的模板函数是一致的
  • 做法:后续新增的类,只需实现超类抽象函数就可以完成扩展

用委托取代继承

  • 场景:子类只使用超类接口的一部分,或者直接不需要继承来的数据
  • 思路:在子类中新建字段保存超类,然后调整子类函数,让它委托超类,然后去掉两者的继承关系
  • 动机:略
  • 做法:略

用继承取代委托

  • 场景:两个类的委托关系过多,且委托函数都很简单
  • 思路:让委托类继承受托类
  • 动机:如果你没有使用所有受托类函数,那么就不要用这个重构方法,继续保持委托关系,使用其他重构方法;另外受托对象可变时,也要注意
  • 做法:略

大型重构

Kent Beck和作者所写

本章介绍了4个大型重构的思路,也是大型程序容易遇到的4个问题

  • 梳理和分析继承体系:往往因为某个继承体系承担的两个甚至更多责任,有一个特征是,某一层级的所有类,子类都以相同形容词开始。可以通过委托的形式,对继承体系做正交化
  • 过程化设计转化为对象设计:往往出现在过程化风格传统语言中。可以将数据记录变为对象,拆分大块行为为小块,然后将行为转移到相关对象中。
  • 分离领域和UI:出现在有GUI的场景中。传统的MVC设计模式就是将领域逻辑分离出来,用接口的方式和UI部分代码对接
  • 提炼继承体系:有的类做了太多工作,里面经常有较多的条件表达式。对于这种,可以借助面向对象中的子类和多态或者策略模式实现

重构与现实

重构在某些角度和技术演进很像。技术的接纳过程类似一条钟形曲线。前段包括先行者和早期接受者,中部大量人群包括早期消费者和晚期消费者,最后则是行动迟缓者。不同人有不同的消费动机。先行者和早期接受者感兴趣的是新技术,“范式转移和突破性思想”的愿景;早期和晚期消费者则关心成熟度、成本、支持程度,以及这种新思想/新产品是否被和他们相似的其他人成功使用

尾声

  • 重构工具能节省你的重构时间
  • 永远记住“两顶帽子”,重构时保持代码功能不变

关于编程相关的书籍已有太多太多,本书相比其他编码相关的技术书籍来说,从技术外的视角来介绍还是挺有意思的。编码无非是求生的一种方式,对于程序员来讲,把生活过好也绝不仅是把代码写好就OK的。书中所写基本是作者对其过去职业和人生经历的一个总结,以tips的形式给出,是“术”而非“道”。是的,本书的介绍思路大概是告诉你一些方法,对你的生活和工作有些帮助的方法,而非构建一个体系,一种思考方式。因此,对于那些三观和做事方法思路基本稳定的人来说,它没法撼动你根本的认识,只能做到具体某个方面的启示和改进。同时,读起来也是相对简单的。不客气地说,这本书应该是最近一年中读到的信息密度最低的书了。不过,其中理财和健身两章,尤其是第55章应该算是全书的精华,对我还是挺有帮助的。

下面分章节,对其中的关键的idea进行摘录。

职业

可能由于作者大多数时间是自由职业者,这一部分介绍没有太多新意

  • 工作是公司的,职业生涯是你自己的
  • 把自己当成公司去思考,你有什么可以卖,相比其他“产品”你的优势是什么
  • 作为程序员,你能提供的基础服务就是创建软件
  • 人际交往不能忽略
  • 通过面试最快捷的方式是让面试官对你产生好感,如果能提前接触就更好了(Really?)
  • 承担更多责任是脱颖而出的一种方式,同时保证自己被注意到
  • 成为自由职业者之后,一定不要忘了自我营销

自我营销

作为自由职业者,作者在自我营销上有些自己的见解

  • 自我营销:打造自我品牌 + 多种媒介 + 持之以恒
  • 一个品牌需要包含:传达的信息(slogan) + 视觉符号(logo) + 一致性 + 曝光率,重点在建立一套预期
  • 不要忘了有效利用社交媒体,为自己积累粉丝
  • 学会演讲
  • 著书有时候不是为了赚钱,而是赢得名声

学习

  • 学习知识最好能将知识用于实践
  • 十步学习法:了解全局、确定范围、定义目标、寻找资源、创建计划、筛选资源、开始学习、动手操作、全面掌握、乐为人师
    • 前6步能让你明确方向和目标,为正式学习做准备
    • 后面4步循环往复,快速迭代,乐为人师能强行提高你的理解程度
  • 有时候导师也很重要
  • 遇到知识短板时,就是你成长的机会

注意力

  • 专注像是一种惯性,是逐渐达到的,不可能一次性达成
  • 番茄工作法:将时间拆分成30分钟的番茄钟(25分钟专心工作 +5分钟休息),通过番茄钟衡量工作量和自己的工作效率(实际工作中太容易被打乱节奏了,比较适合自由职业者)
  • 定额工作法:对于需要定期完成的任务,自我规定周期内需要达到的工作量,承诺然后完成。有时候可以借助大众的监督来坚持
  • 多任务并行通常会因为上下文切换影响工作效率,除非另一项任务不需要花费脑力
  • 电视是时间杀手
  • 为了享受快乐有意识地去做,就不是浪费时间;为了逃避自己应该完成的任务去做,就是浪费时间
  • 分解任务可以有效地减小解决问题的困难程度
  • 任何行动都比不行动要好(主要是行动可以获得反馈)

理财

本篇是全书相对最有信息量的一篇。其中第55章对于自己生涯的描述,甚至比其他所有篇章的介绍都要精华。其他章节可能有粉饰自己的成分,但是第55章“额外馈赠”足够真诚。

  • 资产是使用价值高于维护成本的东西,负债则相反。减少负债,增加资产
  • 自我营销越好,薪酬越好谈
  • 期权是指在未来某个时期前购买一定数量股票的选择权,购买的是期望。买方最大亏损有限,最大盈利无限,有权利没有义务;卖方相反。看涨期权和看跌期权作为买方都需要交期望差额的权利金。到期日时(这是欧式期权的做法,美式期权可以在到期日前任意时间交易),买方可以选择或放弃行权,选择行权时,买方卖出股票,赚取高于权利金的差额;放弃行权时同时放弃权利金。
  • 房地产投资是低风险高负债的投资类型,一方面可以寻求房产出售的机会,另一方面可以通过租金获取稳定收入。当然在租赁房产时,建议选择负责任的物业托管。通过部分租金换取安心。
  • 想要提前退休,需要有资本积累,同时,这个资本还要让你获得足够生活的被动收入。如果有提前退休打算,需要下面几点准备
    • 树立目标,即XX岁前退休
    • 意识到通胀的存在,他会吞噬你赶不上它速度的资产
    • 做有固定收益和被动收入的投资(最好还能赶上通胀)
    • 要想更早退休,自然要比常人花更多精力“开源”和“节流”
  • 真正获得财务成功的唯一方法就是用钱生钱
  • 一边欠债一边存钱是最愚蠢的做法,因为债务利息永远高于存款利息。
    • 存钱之前先把房屋抵押贷款还清(这么做还是有些绝对,还要留些以备急用)
    • 能一次付清就一次付清,除非你能通过提前享受获得超过利息的收益
    • 确保先偿还利息最高的债务
    • 并非所有债务都是不好的,除非你能通过提前享受获得超过利息的收益(如住房贷款和学生贷款)
  • 退休即自由,即可以不以钱作为出发点行事,更形象地说,“从社会中赎回自己的生活”。
  • 买房办理贷款时注意贷款利率是否是固定的
  • 作者最初也是程序员的工作,之后逐渐贷款购置房产用于租赁,通过日常工作还款。之后几次创业都不太成功。但是房产累积越来越多。之后通过个人营销开始创办博客和培训教程,认识大佬后,培训教程逐渐受到欢迎。之后订下退休目标,随着房产带来的被动收入和在线培训(编程 + 健身 + 创业)被动收入逐渐稳定,作者成功上岸退休

健身

  • 同时达到多个健身目标是很难实现的。很难在增肌同时减掉脂肪,同样地,很难在减脂同时增长肌肉
  • 减肥很简单:摄入的卡路里小于燃烧的卡路里。
    • 摄入卡路里通过食物计算,但是烹饪过程对热量也有很大影响
    • 燃烧的卡路里可以根据基础代谢率BMR结合训练消耗计算
  • 增肌需要给肌肉压力,挑选动作的时候注意选择复合动作,如深蹲、硬拉、卧推、杠铃推举等等。注意保持足够的蛋白质摄入
  • 腹肌不是靠增肌得到,而要通过减脂。体脂率下降到特定水平自然能看到腹肌。可以通过高强度间歇式训练(HITT)来减脂
  • 跑步和站立式办公都是简单的燃脂方式

心灵

  • 积极思考不只是外表乐观,而且对健康有益,延年益寿
  • 做事方法的第一步是相信自己有改变的能力,改变不了现状,你至少能改变自己的心态。
  • 爱情不是追逐游戏,你追我逃。更健康的模式是行为上体现出自信,用自然随和且充满自信的态度和别人交往。“我自己感觉很好,我不需要你,但是我觉得你挺有意思的,所以我想更好地了解你”
    • 关键是你要真得能表现出足够的自信,你要对自己足够尊重
    • 做一个绝望的、缺乏自信的人,你会发现自己会真的孤立无援
  • 那些拒绝最终都会把你带到一个想和你在一起的人那里

结束语 & 附录

  • 生活原本比你所厌恶的朝九晚五的工作丰富多彩得多
  • 空头是指,你事先“借”该股票的一些股份并卖出,这会产生空头头寸,最终你需要靠回购这只股票来填补你借入的空头头寸
  • Ⅰ型糖尿病是指自身不能产生胰岛素,Ⅱ型糖尿病是指自身对胰岛素不够敏感
  • 吃垃圾食品不会对健康造成重大影响,但是摄入食品的总量却会影响健康。相比健康食品,垃圾食品带来更高热量的同时,只有较低的饱腹感。因此,达到标准热量所使用的垃圾食品可能会让你长期处于挨饿状态。
  • 水果和蔬菜都是健康食品,热量都不高;高蛋白食物的热量值通常也不怎么高。纤维带来饱腹感的同时有较低的热量
    • 总结来说,未经加工的食品就是最健康的

–END–

0%