How to be a Programmer 中文版

这本书属于「第 2 层书籍」。

文章中的原文来自翻译,所以一定会有翻译不当的地方,如果你自认为英文水平够高,直接阅读 英文原文

提出问题

(1)整体来说,这本书到底在谈些什么?

如何成为一名程序员。

(2)作者在细部说了什么,怎么说的?

还不清楚。

(3)这本书说得有道理吗?

不知道。

(4)这本书跟你有什么关系?

我将成为一名程序员。

文章框架

  • 入门
    • 个人技能
      • 学会 Debug
      • 如何通过分割问题空间 Debug
      • 如何移除一个错误
      • 如何使用日志调试
      • 如何理解性能问题
      • 如何解决性能问题
      • 如何优化循环
      • 如何处理 I/O 开销
      • 如何管理内存
      • 如何处理偶现的 Bug
      • 如何学习设计技能
      • 如何进行实验
    • 团队技能
      • 为什么预估很重要
      • 如何预估编程时间
      • 如何搜索信息
      • 如何把人们作为信息源
      • 如何优雅地写文档
      • 如何在垃圾代码上工作
      • 如何使用源代码控制
      • 如何进行单元测试
      • 毫无头绪?休息一下
      • 如何决定下班时间
      • 如何与不好相处的人相处
  • 进阶
    • 个人技能
      • 如何保持活力
      • 如何才能被广泛信任
      • 在时间和空间之间该如何权衡
      • 如何进行压力测试
      • 如何权衡简洁与抽象
      • 如何学习新技能
      • 学会打字
      • 如何进行集成测试
      • 交流语言
      • 重型工具
      • 如何分析数据
    • 团队技能
      • 如何管理开发时间
      • 如何管理第三方软件风险
      • 如何管理咨询
      • 如何适度交流
      • 如何直言异议以及如何避免
    • 评判
      • 如何权衡开发质量与开发时间
      • 如何管理软件系统依赖
      • 如何评判软件是否太不成熟了
      • 如何决定购买还是构建
      • 如何专业地成长
      • 如何评估面试者
      • 如何决定什么时候使用奇妙的计算机科学
      • 如何与非工程师交谈
  • 高级
    • 技术评判
      • 如何从不可能中找到困难的部分
      • 如何使用嵌入型语言
      • 选择语言
    • 机智地妥协
      • 如何与时间压力作斗争
      • 如何理解用户
      • 如何获得晋升
    • 服务你的团队
      • 如何发展才能
      • 如何选择工作内容
      • 如何让你队友的价值最大化
      • 如何划分问题
      • 如何处理无聊的问题
      • 如何为工程获取支持
      • 如何发展一个系统
      • 如何有效地沟通
      • 如何告诉人们他们不想听的东西
      • 如何处理管理神话
      • 如何处理混乱的组织

摘录

一、引言

做一个好的程序员,困难而高尚。

还没有这种体会

在这篇文章里,我尝试尽可能简洁地总结那些当我 21 岁时,希望别人告诉我的事。

这句话,吸引了我的注意力

在读这篇文章之前,或者就是现在,你当然也应该读一读 Paul Graham [PGSite] 和 Eric Raymond [Hacker] 的文章。

Paul Graham 和 Eric Raymond 都是编程领域最值得我学习的人

在这篇文章里,boss 这个词指的是任何一个交给你工程去做的人。 除了一些语境外,我会同义地使用交易,公司,集体这些词,比如,交易意味着赚钱,公司意味着现代的工作空间,集体一般是那些你一起工作的人。

二、入门

1,个人技能

1.1,学会 Debug

调试的真正含义:测试程序运行。

一个不会调试的程序员等同于瞎子。

调试是一件与程序运行相关的事情,而非与程序本身相关。

更常见的是,你的程序出现了一个错误,当你检查你写的代码的时候,却不知道这个错误是怎么发生的。

为了获得一个程序执行过程的可见性,你必须能够执行代码并且从这个过程中观察到什么。有些时候这是显而易见的,比如一些正在呈现在屏幕上的东西,或者两个事件之间的延迟。另外一些时候,调试与一些不一定可见的东西相关,比如代码中一些变量的状态,哪一行代码正在被执行,或者一些断言是否持有了一个复杂的数据结构。这些隐藏的细节必须被披露出来。

观察一个正在执行程序内部的方法通常可按如下分类:

  • 使用一个调试工具
  • printlining:在严格的临时机制上,在程序中插入一些语句,为调试输出一些程序执行过程中的信息
  • 日志——用日志的形式为了在程序的运行中创建一个永久的视窗

当调试工具可用时,知道如何使用调试工具是一件好事,但学会使用该其他两种方式也是至关重要的。

当调试需要修改代码的时候,一些初学者会感到害怕。这是可以理解的,这有点像探索型外科手术。但你需要学会打破代码,让它跳起来,你需要学会在它上面做实验,并且需要知道你临时对它做的任何事情都不会使它变得更糟。如果你感受到了这份恐惧,找一位导师 - 就是因为许多人在一开始面对这种恐惧的的时候表现的太脆弱,我们因此失去了很多本可以变成优秀程序员的人。

1.2,如何通过分割问题空间 Debug

调试的简单之道——使用分治法。把问题是怎样的一直清楚地记在心里能让我们保持注意力。

从中间划分,那么问题的中点在哪里?

刚开始调试的时候,你看不到一些你稍后开发的时候才会看到的其他纬度,比如执行过的代码段,数据结构,内存管理,与外部代码的交互,一些有风险的代码,一些简单的代码。对于一个有经验的程序员,这些其他的纬度为整个可能出错的事情展示了一个不完美但是有用的思维模型。拥有这样的思维模型能让一个人更高效地找到问题的中点。

1.3,如何移除一个错误

理想状况下,当你完美地发现了错误以及它的修复方法时,你会对代码有完美的理解,并且有一种顿悟的感觉。

在修复 bug 时,你可能想要做最小的改变来修复它。你可能看到一些其他需要改进的东西,但不会同时去改进他们。请使用科学的方法去改进一个东西,并且一次只改变一个东西。修复 bug 最好的方式是能够重现 bug,然后把你的修复替换进去,重新运行你的程序,观察,直到 bug 不再出现。

1.4,如何使用日志调试

Logging(日志)是一种编写系统的方式,可以产生一系列信息记录,被称为 log。Printlining 只是输出简单的,通常是临时的日志。初学者一定要理解并且使用日志,因为他们对编程的理解是局限的。

在理想状态下,程序运行时产生的日志信息数量需要是可配置的。通常,日志提供了下面三个基本的优点:

  • 日志可以提供一些难以重现的 bug 的有效信息,比如在产品环境中发生的、不能在测试环境重现的 bug
  • 日志可以提供统计和与性能相关的调试以处理具体的问题数据,比如语句间流逝过的时间
  • 可配置的情况下,日志允许我们获取普通的信息,使得我们可以在不修改或重新部署代码的情况下

1.5,如何理解性能问题

假如你或你的客户认为你的一个系统或子系统运行太慢了。在你把它变快之前,你必须构建一个它为什么慢的思维模型。

这是为什么?

90% 的时间会花费在 10% 的代码上。

在性能有关的问题中,输入输出开销是重要的

计算机系统的性能有很多个维度,很多资源会被消耗。第一种资源是“挂钟时间”,即执行程序的所有时间。

竞争共享的资源被同步使用,可能导致死锁和互斥。死锁是由于不恰当的同步和请求资源导致线程执行能力的丧失。互斥是对于资源访问的不恰当安排。

1.6,如何修复性能问题

性能是可用性的一部分,而且通常它也需要被更仔细地考虑。

提高一个非常复杂的系统的性能的关键是,充分分析它,来发现其“瓶颈”,或者其资源耗费的地方。一个简要的原则是,你在做任何事情之前必须仔细思考,除非你认为它能够使系统或者它的一个重要部分至少快两倍。

当你在某个方面做了一个两倍提升后,你需要至少重新考虑并且可能重新分析,去发现系统中下一个最昂贵的瓶颈,并且攻破那个瓶颈,得到下一个两倍提升。

1.7,如何优化循环

在你尝试使循环变得快一点之前,花几分钟考虑是否有可能把它整个移除掉,有没有一个不同的算法?你可以在计算时做一些其他的事情吗?如果你不能找到一个方法去绕开它,你可以优化这个循环了。

最后,这不仅需要智慧而且需要理解每一种语句和表达式的开销。一些建议:

  • 删除浮点运算操作
  • 非必要时不要分配新的内存
  • 把常量都放在一起声明
  • 把 I/O 放在缓冲里做
  • 尽量不使用除法
  • 尽量不使用昂贵的类型转换
  • 移动指针而非重新计算索引

这些操作的具体代价取决于你的具体系统。在一些系统中,编译器和硬件会为你做一些事情。但必须清楚,有效的代码比需要在特殊平台下理解的代码要好。

1.8,如何处理 I/O 开销

在很多问题上,处理器的速度比硬件交流要快得多。这种代价通常是小的 I/O,可能包括网络消耗,磁盘 I/O,数据库查询,文件 I/O,还有其他与处理器不太接近的硬件使用。所以构建一个快速的系统通常是提高 I/O,而非在紧凑的循环里优化代码或优化算法的问题。

有两种基本的技术来优化 I/O:caching 和 representation

缓存是通过本地存储数据的副本,再次获取数据时就不需要再执行 I/O,以此来避免 I/O。缓存的关键在于要让哪些数据是主干的,哪些数据是副本变得显而易见。主干的数据只有一份(在一个更新周期里)。缓存有这样一种危险:副本有时候不能立即反映主干的修改。

代表是通过更高效地表示数据来让 I/O 更廉价。这通常会限制其他的要求,比如可读性和可移植性。

1.9,如何管理内存

内存是一种你不可以耗尽的珍贵资源。在一段时间里,你可以无视它,但最终你必须决定如何管理内存。

堆内存是在单一子程序范围外,需要持续(保留)的空间。

如果可以的话,使用垃圾回收器。它会自己注意到垃圾的存在并且在不需要程序员做任何事情的情况下释放它的内存空间。

内存泄漏:把哈希表作为一个缓存,但是忘了删除对哈希表的引用。因为引用仍然存在,被引用者是不可回收但却无用的。

1.10,如何处理偶现的 Bugs

  • 尝试在以一种可控的方式重现 Bug。
  • 通过日志系统记录这种偶现的 Bug。
  • 如果在初步的日志中无法直接得到有关 Bug 的信息,这时就要优化 Bug。

1.11,如何学习设计技能

  • 跟导师学习
  • 学习精心编写过的软件片段
  • 读一些关于最新设计技术的书
  • 动手设计一个小的工程(最后完成时思考:为什么设计成功或失败;怎样偏离最初的设想)
  • 着手一个大工程
  • 发展出自己的风格是自然而有用的(记住:设计是一种艺术,而不是一种技术)不要武断对待特定的设计风格

参考资料

  1. How to be a Programmer 中文版