From 5865876372aa58530ef70f995471e92aab9e5496 Mon Sep 17 00:00:00 2001 From: grissomshen Date: Sat, 18 Jan 2025 17:14:12 +0800 Subject: [PATCH] add chinese version --- .gitignore | 1 + zh/README.md | 10 +++++ zh/SUMMARY.md | 100 ++++++++++++++++++++++++++++++++++++++++++ zh/thing_01/README.md | 15 +++++++ zh/thing_02/README.md | 19 ++++++++ zh/thing_03/README.md | 16 +++++++ zh/thing_04/README.md | 20 +++++++++ zh/thing_05/README.md | 28 ++++++++++++ zh/thing_06/README.md | 19 ++++++++ zh/thing_07/README.md | 21 +++++++++ zh/thing_08/README.md | 15 +++++++ zh/thing_09/README.md | 21 +++++++++ zh/thing_10/README.md | 29 ++++++++++++ zh/thing_11/README.md | 40 +++++++++++++++++ zh/thing_12/README.md | 17 +++++++ zh/thing_13/README.md | 15 +++++++ zh/thing_14/README.md | 15 +++++++ zh/thing_15/README.md | 25 +++++++++++ zh/thing_16/README.md | 15 +++++++ zh/thing_17/README.md | 13 ++++++ zh/thing_18/README.md | 28 ++++++++++++ zh/thing_19/README.md | 21 +++++++++ zh/thing_20/README.md | 15 +++++++ zh/thing_21/README.md | 17 +++++++ zh/thing_22/README.md | 25 +++++++++++ zh/thing_23/README.md | 15 +++++++ zh/thing_24/README.md | 13 ++++++ zh/thing_25/README.md | 26 +++++++++++ zh/thing_26/README.md | 47 ++++++++++++++++++++ zh/thing_27/README.md | 13 ++++++ zh/thing_28/README.md | 19 ++++++++ zh/thing_29/README.md | 21 +++++++++ zh/thing_30/README.md | 27 ++++++++++++ zh/thing_31/README.md | 22 ++++++++++ zh/thing_32/README.md | 15 +++++++ zh/thing_33/README.md | 17 +++++++ zh/thing_34/README.md | 13 ++++++ zh/thing_35/README.md | 13 ++++++ zh/thing_36/README.md | 17 +++++++ zh/thing_37/README.md | 13 ++++++ zh/thing_38/README.md | 23 ++++++++++ zh/thing_39/README.md | 24 ++++++++++ zh/thing_40/README.md | 24 ++++++++++ zh/thing_41/README.md | 13 ++++++ zh/thing_42/README.md | 15 +++++++ zh/thing_43/README.md | 13 ++++++ zh/thing_44/README.md | 19 ++++++++ zh/thing_45/README.md | 23 ++++++++++ zh/thing_46/README.md | 47 ++++++++++++++++++++ zh/thing_47/README.md | 19 ++++++++ zh/thing_48/README.md | 15 +++++++ zh/thing_49/README.md | 19 ++++++++ zh/thing_50/README.md | 31 +++++++++++++ zh/thing_51/README.md | 39 ++++++++++++++++ zh/thing_52/README.md | 17 +++++++ zh/thing_53/README.md | 52 ++++++++++++++++++++++ zh/thing_54/README.md | 33 ++++++++++++++ zh/thing_55/README.md | 16 +++++++ zh/thing_56/README.md | 19 ++++++++ zh/thing_57/README.md | 19 ++++++++ zh/thing_58/README.md | 23 ++++++++++ zh/thing_59/README.md | 51 +++++++++++++++++++++ zh/thing_60/README.md | 17 +++++++ zh/thing_61/README.md | 15 +++++++ zh/thing_62/README.md | 13 ++++++ zh/thing_63/README.md | 15 +++++++ zh/thing_64/README.md | 25 +++++++++++ zh/thing_65/README.md | 31 +++++++++++++ zh/thing_66/README.md | 27 ++++++++++++ zh/thing_67/README.md | 19 ++++++++ zh/thing_68/README.md | 19 ++++++++ zh/thing_69/README.md | 50 +++++++++++++++++++++ zh/thing_70/README.md | 13 ++++++ zh/thing_71/README.md | 13 ++++++ zh/thing_72/README.md | 15 +++++++ zh/thing_73/README.md | 25 +++++++++++ zh/thing_74/README.md | 13 ++++++ zh/thing_75/README.md | 19 ++++++++ zh/thing_76/README.md | 41 +++++++++++++++++ zh/thing_77/README.md | 23 ++++++++++ zh/thing_78/README.md | 29 ++++++++++++ zh/thing_79/README.md | 13 ++++++ zh/thing_80/README.md | 15 +++++++ zh/thing_81/README.md | 45 +++++++++++++++++++ zh/thing_82/README.md | 15 +++++++ zh/thing_83/README.md | 11 +++++ zh/thing_84/README.md | 39 ++++++++++++++++ zh/thing_85/README.md | 23 ++++++++++ zh/thing_86/README.md | 23 ++++++++++ zh/thing_87/README.md | 15 +++++++ zh/thing_88/README.md | 31 +++++++++++++ zh/thing_89/README.md | 37 ++++++++++++++++ zh/thing_90/README.md | 17 +++++++ zh/thing_91/README.md | 50 +++++++++++++++++++++ zh/thing_92/README.md | 13 ++++++ zh/thing_93/README.md | 13 ++++++ zh/thing_94/README.md | 26 +++++++++++ zh/thing_95/README.md | 15 +++++++ zh/thing_96/README.md | 23 ++++++++++ zh/thing_97/README.md | 13 ++++++ 100 files changed, 2264 insertions(+) create mode 100644 zh/README.md create mode 100644 zh/SUMMARY.md create mode 100644 zh/thing_01/README.md create mode 100644 zh/thing_02/README.md create mode 100644 zh/thing_03/README.md create mode 100644 zh/thing_04/README.md create mode 100644 zh/thing_05/README.md create mode 100644 zh/thing_06/README.md create mode 100644 zh/thing_07/README.md create mode 100644 zh/thing_08/README.md create mode 100644 zh/thing_09/README.md create mode 100644 zh/thing_10/README.md create mode 100644 zh/thing_11/README.md create mode 100644 zh/thing_12/README.md create mode 100644 zh/thing_13/README.md create mode 100644 zh/thing_14/README.md create mode 100644 zh/thing_15/README.md create mode 100644 zh/thing_16/README.md create mode 100644 zh/thing_17/README.md create mode 100644 zh/thing_18/README.md create mode 100644 zh/thing_19/README.md create mode 100644 zh/thing_20/README.md create mode 100644 zh/thing_21/README.md create mode 100644 zh/thing_22/README.md create mode 100644 zh/thing_23/README.md create mode 100644 zh/thing_24/README.md create mode 100644 zh/thing_25/README.md create mode 100644 zh/thing_26/README.md create mode 100644 zh/thing_27/README.md create mode 100644 zh/thing_28/README.md create mode 100644 zh/thing_29/README.md create mode 100644 zh/thing_30/README.md create mode 100644 zh/thing_31/README.md create mode 100644 zh/thing_32/README.md create mode 100644 zh/thing_33/README.md create mode 100644 zh/thing_34/README.md create mode 100644 zh/thing_35/README.md create mode 100644 zh/thing_36/README.md create mode 100644 zh/thing_37/README.md create mode 100644 zh/thing_38/README.md create mode 100644 zh/thing_39/README.md create mode 100644 zh/thing_40/README.md create mode 100644 zh/thing_41/README.md create mode 100644 zh/thing_42/README.md create mode 100644 zh/thing_43/README.md create mode 100644 zh/thing_44/README.md create mode 100644 zh/thing_45/README.md create mode 100644 zh/thing_46/README.md create mode 100644 zh/thing_47/README.md create mode 100644 zh/thing_48/README.md create mode 100644 zh/thing_49/README.md create mode 100644 zh/thing_50/README.md create mode 100644 zh/thing_51/README.md create mode 100644 zh/thing_52/README.md create mode 100644 zh/thing_53/README.md create mode 100644 zh/thing_54/README.md create mode 100644 zh/thing_55/README.md create mode 100644 zh/thing_56/README.md create mode 100644 zh/thing_57/README.md create mode 100644 zh/thing_58/README.md create mode 100644 zh/thing_59/README.md create mode 100644 zh/thing_60/README.md create mode 100644 zh/thing_61/README.md create mode 100644 zh/thing_62/README.md create mode 100644 zh/thing_63/README.md create mode 100644 zh/thing_64/README.md create mode 100644 zh/thing_65/README.md create mode 100644 zh/thing_66/README.md create mode 100644 zh/thing_67/README.md create mode 100644 zh/thing_68/README.md create mode 100644 zh/thing_69/README.md create mode 100644 zh/thing_70/README.md create mode 100644 zh/thing_71/README.md create mode 100644 zh/thing_72/README.md create mode 100644 zh/thing_73/README.md create mode 100644 zh/thing_74/README.md create mode 100644 zh/thing_75/README.md create mode 100644 zh/thing_76/README.md create mode 100644 zh/thing_77/README.md create mode 100644 zh/thing_78/README.md create mode 100644 zh/thing_79/README.md create mode 100644 zh/thing_80/README.md create mode 100644 zh/thing_81/README.md create mode 100644 zh/thing_82/README.md create mode 100644 zh/thing_83/README.md create mode 100644 zh/thing_84/README.md create mode 100644 zh/thing_85/README.md create mode 100644 zh/thing_86/README.md create mode 100644 zh/thing_87/README.md create mode 100644 zh/thing_88/README.md create mode 100644 zh/thing_89/README.md create mode 100644 zh/thing_90/README.md create mode 100644 zh/thing_91/README.md create mode 100644 zh/thing_92/README.md create mode 100644 zh/thing_93/README.md create mode 100644 zh/thing_94/README.md create mode 100644 zh/thing_95/README.md create mode 100644 zh/thing_96/README.md create mode 100644 zh/thing_97/README.md diff --git a/.gitignore b/.gitignore index 6f92564c..c0cc483e 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ _book # other .DS_Store +.idea diff --git a/zh/README.md b/zh/README.md new file mode 100644 index 00000000..87e2821d --- /dev/null +++ b/zh/README.md @@ -0,0 +1,10 @@ +每个程序员都应该知道的97件事 +====== + +*来自领先从业者的程序员智慧结晶。* + +这是['每个程序员都应该知道的97件事'项目](http://programmer.97things.oreilly.com/wiki/index.php/97_Things_Every_Programmer_Should_Know)的[GitBook](https://www.gitbook.io)版本。 + +所有内容均根据[Creative Commons Attribution-NonCommercial-ShareAlike 3.0许可证](http://creativecommons.org/licenses/by-nc-sa/3.0/)授权。书籍的印刷版可在[Amazon.com](http://www.amazon.com/Things-Every-Programmer-Should-Know/dp/0596809484)上购买。 + +如果您发现任何错误或有任何建议,可以在[仓库](https://github.com/97-things/97-things-every-programmer-should-know)中[创建问题](https://github.com/97-things/97-things-every-programmer-should-know/issues)或[提交拉取请求](https://github.com/97-things/97-things-every-programmer-should-know/pulls)。 \ No newline at end of file diff --git a/zh/SUMMARY.md b/zh/SUMMARY.md new file mode 100644 index 00000000..1e165b86 --- /dev/null +++ b/zh/SUMMARY.md @@ -0,0 +1,100 @@ +# 摘要 + +* [介绍](README.md) +1. [谨慎行事](thing_01/README.md) +1. [应用函数式编程原则](thing_02/README.md) +1. [问“用户会怎么做?”(你不是用户)](thing_03/README.md) +1. [自动化你的编码标准](thing_04/README.md) +1. [美在于简洁](thing_05/README.md) +1. [重构之前](thing_06/README.md) +1. [小心共享](thing_07/README.md) +1. [童子军规则](thing_08/README.md) +1. [在责怪他人之前先检查你的代码](thing_09/README.md) +1. [谨慎选择工具](thing_10/README.md) +1. [用领域语言编写代码](thing_11/README.md) +1. [代码即设计](thing_12/README.md) +1. [代码布局很重要](thing_13/README.md) +1. [代码审查](thing_14/README.md) +1. [理性编码](thing_15/README.md) +1. [关于注释的评论](thing_16/README.md) +1. [只注释代码无法表达的内容](thing_17/README.md) +1. [持续学习](thing_18/README.md) +1. [便利性不是一种能力](thing_19/README.md) +1. [尽早且频繁地部署](thing_20/README.md) +1. [区分业务异常和技术异常](thing_21/README.md) +1. [进行大量刻意练习](thing_22/README.md) +1. [领域特定语言](thing_23/README.md) +1. [不要害怕破坏东西](thing_24/README.md) +1. [不要用测试数据耍小聪明](thing_25/README.md) +1. [不要忽略那个错误!](thing_26/README.md) +1. [不要只学习语言,理解其文化](thing_27/README.md) +1. [不要把你的程序钉死在直立位置](thing_28/README.md) +1. [不要依赖“魔法发生在这里”](thing_29/README.md) +1. [不要重复自己](thing_30/README.md) +1. [不要碰那段代码!](thing_31/README.md) +1. [封装行为,而不仅仅是状态](thing_32/README.md) +1. [浮点数不是实数](thing_33/README.md) +1. [通过开源实现你的抱负](thing_34/README.md) +1. [API设计的黄金法则](thing_35/README.md) +1. [大师神话](thing_36/README.md) +1. [努力工作不会带来回报](thing_37/README.md) +1. [如何使用缺陷跟踪器](thing_38/README.md) +1. [通过删除代码来改进代码](thing_39/README.md) +1. [安装我](thing_40/README.md) +1. [进程间通信影响应用程序响应时间](thing_41/README.md) +1. [保持构建干净](thing_42/README.md) +1. [知道如何使用命令行工具](thing_43/README.md) +1. [精通两种以上的编程语言](thing_44/README.md) +1. [了解你的IDE](thing_45/README.md) +1. [了解你的极限](thing_46/README.md) +1. [知道你的下一次提交](thing_47/README.md) +1. [大型互连数据属于数据库](thing_48/README.md) +1. [学习外语](thing_49/README.md) +1. [学会估算](thing_50/README.md) +1. [学会说“Hello, World”](thing_51/README.md) +1. [让你的项目为自己说话](thing_52/README.md) +1. [链接器不是一个神奇的程序](thing_53/README.md) +1. [临时解决方案的长寿](thing_54/README.md) +1. [使接口易于正确使用,难以错误使用](thing_55/README.md) +1. [让不可见的更可见](thing_56/README.md) +1. [消息传递在并行系统中带来更好的可扩展性](thing_57/README.md) +1. [给未来的信息](thing_58/README.md) +1. [多态性的缺失机会](thing_59/README.md) +1. [怪异新闻:测试人员是你的朋友](thing_60/README.md) +1. [一个二进制文件](thing_61/README.md) +1. [只有代码能说明真相](thing_62/README.md) +1. [拥有(并重构)构建](thing_63/README.md) +1. [结对编程并感受流程](thing_64/README.md) +1. [优先使用领域特定类型而非原始类型](thing_65/README.md) +1. [预防错误](thing_66/README.md) +1. [专业程序员](thing_67/README.md) +1. [将所有内容置于版本控制之下](thing_68/README.md) +1. [放下鼠标,远离键盘](thing_69/README.md) +1. [阅读代码](thing_70/README.md) +1. [阅读人文学科](thing_71/README.md) +1. [经常重新发明轮子](thing_72/README.md) +1. [抵制单例模式的诱惑](thing_73/README.md) +1. [通往性能的道路上布满了肮脏的代码炸弹](thing_74/README.md) +1. [简洁来自于减少](thing_75/README.md) +1. [单一职责原则](thing_76/README.md) +1. [从“是”开始](thing_77/README.md) +1. [退后一步,自动化,自动化,自动化](thing_78/README.md) +1. [利用代码分析工具](thing_79/README.md) +1. [测试所需行为,而非偶然行为](thing_80/README.md) +1. [精确而具体地测试](thing_81/README.md) +1. [在你睡觉时(和周末)测试](thing_82/README.md) +1. [测试是软件开发的工程严谨性](thing_83/README.md) +1. [状态思维](thing_84/README.md) +1. [两个脑袋通常比一个好](thing_85/README.md) +1. [两个错误可以变成一个正确(且难以修复)](thing_86/README.md) +1. [为你的朋友编写Ubuntu代码](thing_87/README.md) +1. [Unix工具是你的朋友](thing_88/README.md) +1. [使用正确的算法和数据结构](thing_89/README.md) +1. [冗长的日志会打扰你的睡眠](thing_90/README.md) +1. [WET稀释性能瓶颈](thing_91/README.md) +1. [当程序员和测试人员合作时](thing_92/README.md) +1. [编写代码时,假设你需要终身支持它](thing_93/README.md) +1. [使用示例编写小函数](thing_94/README.md) +1. [为人们编写测试](thing_95/README.md) +1. [你必须关心代码](thing_96/README.md) +1. [你的客户并不总是言出必行](thing_97/README.md) \ No newline at end of file diff --git a/zh/thing_01/README.md b/zh/thing_01/README.md new file mode 100644 index 00000000..677d1f3d --- /dev/null +++ b/zh/thing_01/README.md @@ -0,0 +1,15 @@ +# 谨慎行事 + +> *“无论你承担什么任务,都要谨慎行事并考虑后果。” —— 无名氏* + +无论一个迭代开始时的时间表看起来多么舒适,你都无法避免在某些时候处于压力之下。如果你发现自己必须在“正确完成”和“快速完成”之间做出选择,通常“快速完成”会更吸引人,因为你理解可以在以后回来修复它。当你对自己、团队和客户做出这个承诺时,你是认真的。但往往在下一个迭代中,新的问题会出现,你会把注意力集中在它们身上。这种被推迟的工作被称为技术债务,它并不是你的朋友。具体来说,Martin Fowler 在他的[技术债务分类](http://martinfowler.com/bliki/TechnicalDebtQuadrant.html)中将其称为故意技术债务,不应与无意技术债务混淆。 + +技术债务就像一笔贷款:你在短期内从中受益,但在完全还清之前,你必须支付利息。代码中的捷径使得添加功能或重构代码变得更加困难。它们是缺陷和脆弱测试用例的温床。你拖延得越久,情况就会变得越糟。当你终于开始处理最初的修复时,可能已经有一堆不太正确的设计选择叠加在原始问题之上,使得代码更难重构和修正。事实上,通常只有当事情变得非常糟糕以至于你必须修复时,你才会真正回去修复它。而到那时,修复往往变得非常困难,以至于你根本无法承担所需的时间或风险。 + +有时你必须为了赶在截止日期前完成任务或实现某个功能的薄切片而承担技术债务。尽量避免这种情况,但如果情况确实需要,那就去做吧。但是(这是一个很大的“但是”),你必须跟踪技术债务并尽快偿还,否则情况会迅速恶化。一旦你决定妥协,立即写一张任务卡片或在问题跟踪系统中记录下来,以确保它不会被遗忘。 + +如果你在下一个迭代中安排偿还债务,成本将是最小的。未偿还的债务会积累利息,这些利息应该被跟踪,以使成本可见。这将强调技术债务对项目业务价值的影响,并使偿还的优先级得到适当的安排。如何计算和跟踪利息的选择将取决于具体的项目,但你必须跟踪它。 + +尽快偿还技术债务。否则就是不谨慎的。 + +作者:[Seb Rose](http://programmer.97things.oreilly.com/wiki/index.php/Seb_Rose) \ No newline at end of file diff --git a/zh/thing_02/README.md b/zh/thing_02/README.md new file mode 100644 index 00000000..2f420847 --- /dev/null +++ b/zh/thing_02/README.md @@ -0,0 +1,19 @@ +# 应用函数式编程原则 + +函数式编程最近在主流编程社区中重新引起了兴趣。部分原因是因为函数式范式的*涌现特性*非常适合应对我们行业向多核转变所带来的挑战。然而,尽管这无疑是一个重要的应用场景,但这并不是本文劝诫你*了解函数式编程*的原因。 + +掌握函数式编程范式可以极大地提高你在其他上下文中编写的代码质量。如果你深入理解并应用函数式范式,你的设计将展现出更高程度的*引用透明性*。 + +引用透明性是一个非常理想的特性:它意味着函数在给定相同输入的情况下始终产生相同的结果,无论它们在何时何地被调用。也就是说,函数的求值过程较少依赖于可变状态的副作用——理想情况下,完全不依赖。 + +命令式代码中缺陷的一个主要原因是可变变量。阅读本文的每个人都曾调查过为什么在某些情况下某个值不符合预期。可见性语义可以帮助缓解这些潜在的缺陷,或者至少大大缩小它们的位置范围,但真正的罪魁祸首实际上可能是设计中过度使用可变性。 + +在这方面,我们当然没有得到行业的太多帮助。面向对象编程的入门教程默许了这种设计,因为它们通常展示的示例是由相对长寿命的对象图组成的,这些对象愉快地互相调用可变方法,这可能是危险的。然而,通过精明的测试驱动设计,特别是确保“模拟角色,而不是对象”,可以设计出不必要的可变性。 + +最终的结果是一个通常具有更好责任分配的设计,其中包含更多的小函数,这些函数作用于传递给它们的参数,而不是引用可变的成员变量。缺陷会更少,而且通常更容易调试,因为在这些设计中更容易定位到引入错误值的位置,而不是推断出导致错误赋值的特定上下文。这大大提高了引用透明性,而学习一门函数式编程语言是让这些理念深入骨髓的最佳方式,因为在这种计算模型中,引用透明性是常态。 + +当然,这种方法并非在所有情况下都是最优的。例如,在面向对象系统中,这种风格通常在领域模型开发(即协作有助于分解业务规则的复杂性)中比在用户界面开发中产生更好的结果。 + +掌握函数式编程范式,以便你能够明智地将所学到的经验应用到其他领域。你的对象系统(至少)将因引用透明性的优点而受益,并且比许多人让你相信的更接近函数式编程的对应物。事实上,有些人甚至断言函数式编程和面向对象编程的巅峰*仅仅是彼此的反映*,一种计算中的阴阳形式。 + +作者:[Edward Garson](http://programmer.97things.oreilly.com/wiki/index.php/Edward_Garson) \ No newline at end of file diff --git a/zh/thing_03/README.md b/zh/thing_03/README.md new file mode 100644 index 00000000..4214acfc --- /dev/null +++ b/zh/thing_03/README.md @@ -0,0 +1,16 @@ +# 询问“用户会怎么做?”(你不是用户) + +我们往往倾向于认为其他人的思维方式与我们相同。但事实并非如此。心理学家将这种现象称为“虚假共识偏差”。当人们的想法或行为与我们不同时,我们很可能会(潜意识地)给他们贴上某种“缺陷”的标签。 + +这种偏差解释了为什么程序员很难站在用户的角度思考问题。用户的思维方式与程序员不同。首先,他们使用计算机的时间要少得多。他们既不了解也不关心计算机的工作原理。这意味着他们无法利用程序员所熟悉的各种解决问题的技巧。他们无法识别程序员在界面中使用的模式与线索。 + +了解用户思维方式的最佳方法是观察他们。让用户使用与你正在开发的软件类似的软件来完成一项任务。确保任务是一个真实的任务:“将一列数字相加”是可以的;“计算你上个月的开支”则更好。避免过于具体的任务,比如“你能选择这些电子表格单元格并在下面输入一个 *SUM* 公式吗?”——这个问题中已经给出了很大的提示。让用户一边操作一边讲述他们的进展。不要打断他们,也不要试图提供帮助。不断问自己:“他为什么要这样做?”以及“她为什么不那样做?” + +你首先会注意到的是,用户在某些核心行为上是相似的。他们会以相同的顺序尝试完成任务——并且在相同的地方犯相同的错误。你应该围绕这些核心行为进行设计。这与设计会议不同,在设计会议上,人们往往会因为提出“如果用户想要……怎么办?”而受到关注。这会导致复杂的功能和对用户需求的混淆。观察用户可以消除这种混淆。 + +你会看到用户卡住。当你卡住时,你会四处寻找解决方案。而当用户卡住时,他们会缩小注意力范围。这使得他们更难看到屏幕上其他地方提供的解决方案。这也是为什么帮助文本对于糟糕的用户界面设计来说是一个糟糕的解决方案的原因之一。如果你必须提供说明或帮助文本,请确保将其放置在问题区域的旁边。用户注意力范围的狭窄性解释了为什么工具提示比帮助菜单更有用。 + +用户往往会凑合着用。他们会找到一种有效的方法,并坚持使用它,无论这种方法多么复杂。提供一个非常明显的操作方式,比提供两三个快捷方式更好。 +你还会发现,用户所说的需求与他们实际的行为之间存在差距。这令人担忧,因为收集用户需求的常规方法是询问他们。这就是为什么捕捉需求的最佳方法是观察用户。花一个小时观察用户,比花一天时间猜测他们的需求更有价值。 + +作者:[Giles Colborne](http://programmer.97things.oreilly.com/wiki/index.php/Giles_Colborne) \ No newline at end of file diff --git a/zh/thing_04/README.md b/zh/thing_04/README.md new file mode 100644 index 00000000..e4f9537e --- /dev/null +++ b/zh/thing_04/README.md @@ -0,0 +1,20 @@ +# 自动化你的编码标准 + +你可能也经历过这种情况。在项目开始时,每个人都有很多美好的愿望——可以称之为“新项目的决心”。通常,这些决心中的许多都会被记录在文档中。关于代码的部分最终会写入项目的编码标准中。在项目启动会议上,首席开发人员会逐条讲解文档,最好的情况下,大家都会同意尽力遵守这些标准。然而,一旦项目进入正轨,这些美好的愿望就会一个接一个地被抛弃。当项目最终交付时,代码看起来一团糟,似乎没有人知道为什么会变成这样。 + +问题出在哪里?很可能在项目启动会议上就已经埋下了隐患。一些项目成员没有认真听,另一些没有理解其中的要点。更糟糕的是,有些人不同意这些标准,并且已经在计划如何反抗这些编码标准。最后,有些人理解了并同意了这些标准,但当项目压力过大时,他们不得不放弃一些东西。格式良好的代码并不会让客户给你加分,尤其是当客户更关注功能时。此外,如果没有自动化,遵循编码标准可能是一项相当枯燥的任务。你可以试着手动为一个混乱的类进行缩进,自己体会一下。 + +但如果这是一个如此大的问题,为什么我们一开始还要制定编码标准呢?统一代码格式的一个原因是,没有人可以通过以自己私有的方式格式化代码来“拥有”某段代码。我们可能希望防止开发者使用某些反模式,以避免一些常见的错误。总的来说,编码标准应该让项目中的工作更加轻松,并保持开发速度从始至终。因此,每个人都应该同意编码标准——如果一个开发者使用三个空格缩进代码,而另一个使用四个空格,这并没有什么帮助。 + +有许多工具可以用来生成代码质量报告,并记录和维护编码标准,但这并不是完整的解决方案。应该尽可能地自动化并强制执行这些标准。以下是一些例子: + +- 确保代码格式化是构建过程的一部分,这样每个人在编译代码时都会自动运行它。 +- 使用静态代码分析工具扫描代码,查找不需要的反模式。如果发现任何问题,中断构建。 +- 学会配置这些工具,以便你可以扫描项目中特定的反模式。 +- 不仅要测量测试覆盖率,还要自动检查结果。如果测试覆盖率太低,再次中断构建。 + +尽量为你认为重要的所有事情都这样做。你无法自动化所有你真正关心的事情。对于那些你无法自动标记或修复的事情,可以将它们视为编码标准的补充指南,但要接受你和你的同事可能不会那么严格地遵循它们。 + +最后,编码标准应该是动态的,而不是静态的。随着项目的发展,项目的需求会发生变化,一开始看起来明智的做法,几个月后可能就不再明智了。 + +作者:[Filip van Laenen](http://programmer.97things.oreilly.com/wiki/index.php/Filip_van_Laenen) \ No newline at end of file diff --git a/zh/thing_05/README.md b/zh/thing_05/README.md new file mode 100644 index 00000000..65b41a7d --- /dev/null +++ b/zh/thing_05/README.md @@ -0,0 +1,28 @@ +# 美在于简洁 + +有一句名言,我认为对所有软件开发人员来说都特别值得铭记于心: + +> *风格之美、和谐、优雅和良好的节奏都依赖于简洁。* —— 柏拉图 + +我认为这句话总结了我们作为软件开发人员应该追求的价值。 + +在我们的代码中,我们追求许多东西: + +- 可读性 +- 可维护性 +- 开发速度 +- 难以捉摸的美感 + +柏拉图告诉我们,所有这些品质的促成因素都是简洁。 + +什么是美丽的代码?这可能是一个非常主观的问题。对美的感知在很大程度上取决于个人的背景,就像我们对任何事物的感知都取决于我们的背景一样。接受过艺术教育的人与接受过科学教育的人对美的感知(或至少是方法)是不同的。艺术专业的学生倾向于通过将软件与艺术作品进行比较来探讨软件中的美,而科学专业的学生则倾向于谈论对称性和黄金比例,试图将事物简化为公式。根据我的经验,简洁是双方大多数论点的基础。 + +回想一下你研究过的源代码。如果你没有花时间研究别人的代码,现在就停止阅读这篇文章,去找一些开源代码来研究。说真的!我是认真的!去网上搜索一些用你选择的语言编写的代码,这些代码是由一些公认的专家编写的。 + +你回来了吗?很好。我们说到哪儿了?啊,对了……我发现那些与我产生共鸣并且我认为美丽的代码有一些共同的特点。其中最主要的是简洁。我发现,无论整个应用程序或系统有多复杂,各个部分都必须保持简单。简单的对象只承担单一职责,包含同样简单、专注且具有描述性名称的方法。有些人认为五到十行代码的短方法是极端的,有些语言很难做到这一点,但我认为这种简洁仍然是一个值得追求的目标。 + +归根结底,美丽的代码就是简洁的代码。每个单独的部分都保持简单,职责简单,与系统其他部分的关系也简单。通过这种方式,我们可以保持系统的可维护性,使用干净、简单、可测试的代码,在整个系统的生命周期中保持较高的开发速度。 + +美生于简洁,也存在于简洁之中。 + +作者:Jørn Ølmheim \ No newline at end of file diff --git a/zh/thing_06/README.md b/zh/thing_06/README.md new file mode 100644 index 00000000..cb585318 --- /dev/null +++ b/zh/thing_06/README.md @@ -0,0 +1,19 @@ +# 重构之前 + +每个程序员在某个时刻都需要重构现有的代码。但在你开始之前,请考虑以下几点,这可能会为你和其他人节省大量时间(和痛苦): + +- **重构的最佳方法是从评估现有代码库及其对应的测试开始。** 这将帮助你理解当前代码的优势和劣势,从而确保你保留优点并避免错误。我们都认为自己可以比现有系统做得更好……直到我们最终得到的东西并不比之前的版本更好——甚至更糟——因为我们没有从现有系统的错误中吸取教训。 + +- **避免重写一切的诱惑。** 最好尽可能多地重用现有代码。无论代码多么丑陋,它已经经过了测试、审查等。抛弃旧代码——尤其是如果它已经在生产环境中运行——意味着你抛弃了数月(或数年)经过测试、经过实战考验的代码,这些代码可能包含了你不知道的某些变通方法和错误修复。如果你不考虑这一点,你编写的新代码可能会再次出现旧代码中已经修复的神秘错误。这将浪费大量时间、精力和多年来积累的知识。 + +- **多次小规模修改比一次大规模修改更好。** 小规模修改允许你通过反馈(例如测试)更容易地评估对系统的影响。在做出更改后看到一百个测试失败并不有趣。这可能会导致沮丧和压力,进而导致糟糕的决策。几个测试失败很容易处理,并提供了一种更可控的方法。 + +- **在每次迭代之后,确保现有测试通过非常重要。** 如果现有测试不足以覆盖你所做的更改,请添加新的测试。不要轻易抛弃旧代码的测试。表面上,其中一些测试可能看起来不适用于你的新设计,但深入挖掘这些测试添加的原因是非常值得的。 + +- **个人偏好和自尊心不应成为障碍。** 如果某些东西没有坏,为什么要修复它?代码的风格或结构不符合你的个人偏好并不是重构的有效理由。认为你可以比之前的程序员做得更好也不是一个有效的理由。 + +- **新技术不足以成为重构的理由。** 重构的最糟糕理由之一是当前代码远远落后于我们今天拥有的所有酷炫技术,并且我们相信新的语言或框架可以更优雅地完成工作。除非成本效益分析表明新语言或框架将显著改善功能、可维护性或生产力,否则最好保持现状。 + +- **记住人类会犯错误。** 重构并不总是保证新代码会比之前的尝试更好——甚至一样好。我见过并参与过几次失败的重构尝试。这并不美好,但这是人之常情。 + +作者:[Rajith Attapattu](http://programmer.97things.oreilly.com/wiki/index.php/Rajith_Attapattu) \ No newline at end of file diff --git a/zh/thing_07/README.md b/zh/thing_07/README.md new file mode 100644 index 00000000..21cefa24 --- /dev/null +++ b/zh/thing_07/README.md @@ -0,0 +1,21 @@ +# 警惕共享 + +这是我在公司的第一个项目。我刚完成学位,急于证明自己,每天加班加点地阅读现有代码。在处理我的第一个功能时,我格外小心地应用了我所学的所有知识——添加注释、记录日志、尽可能将共享代码提取到库中,等等。我本以为已经准备充分的代码审查却让我大吃一惊——重用代码竟然不受欢迎! + +怎么会这样?在整个大学期间,重用代码一直被奉为高质量软件工程的典范。我读过的所有文章、教科书,以及那些经验丰富的软件专业人士教给我的东西,难道都错了吗? + +事实证明,我忽略了一个关键因素。 + +**上下文**。 + +系统中两个截然不同的部分以相同的方式执行某些逻辑,这一事实的意义远没有我想象的那么重要。在我提取出这些共享代码库之前,这些部分彼此之间并没有依赖关系。每个部分都可以独立发展。每个部分都可以根据系统业务环境的变化调整其逻辑。那几行相似的代码是偶然的——一种暂时的异常,一种巧合。直到我出现之前,情况一直如此。 + +我创建的共享代码库将两只脚的鞋带绑在了一起。一个业务领域的步骤必须与另一个业务领域同步才能进行。这些独立功能的维护成本曾经可以忽略不计,但共享库需要的测试工作量却增加了一个数量级。 + +虽然我减少了系统中代码的绝对行数,但却增加了依赖关系的数量。这些依赖关系的上下文至关重要——如果它们是局部的,可能还情有可原,甚至有一定的积极价值。但当这些依赖关系不受控制时,它们的触角会纠缠到系统的更大问题中,尽管代码本身看起来还不错。 + +这些错误的阴险之处在于,它们的核心听起来像是个好主意。在正确的上下文中应用这些技术是有价值的。但在错误的上下文中,它们会增加成本而不是价值。如今,当我进入一个现有的代码库,并且对各个部分的使用上下文一无所知时,我会更加谨慎地对待共享的内容。 + +**警惕共享。检查你的上下文。只有这样,才能继续前进。** + +作者:[Udi Dahan](http://programmer.97things.oreilly.com/wiki/index.php/Udi_Dahan) \ No newline at end of file diff --git a/zh/thing_08/README.md b/zh/thing_08/README.md new file mode 100644 index 00000000..a284609c --- /dev/null +++ b/zh/thing_08/README.md @@ -0,0 +1,15 @@ +# 童子军规则 + +童子军有一条规则:“永远让营地比你发现时更干净。”如果你发现地上有垃圾,不管是谁弄的,你都要清理干净。你特意为下一批露营者改善环境。实际上,这条规则的原始形式是由童子军之父罗伯特·斯蒂芬森·史密斯·巴登-鲍威尔(Robert Stephenson Smyth Baden-Powell)所写的:“努力让这个世界比你发现时更好一点。” + +如果我们在代码中也遵循类似的规则会怎样:“永远让模块在你提交时比你检出时更干净。”无论最初的作者是谁,如果我们总是努力,无论多么微小,去改进这个模块,结果会怎样? + +我认为如果我们都遵循这条简单的规则,我们将看到软件系统不断恶化的终结。相反,我们的系统会随着演化逐渐变得更好。我们还会看到*团队*作为一个整体来关心系统,而不仅仅是个人关心他们自己的一小部分。 + +我不认为这条规则要求太多。你不必在提交之前让每个模块都变得完美。你只需要让它比你检出时*好一点点*。当然,这意味着你*添加*到模块中的任何代码都必须是干净的。这也意味着你在提交模块之前至少要清理一个其他问题。你可能只是改进一个变量的名称,或者将一个长函数拆分成两个较小的函数。你可能打破一个循环依赖,或者添加一个接口来将策略与细节解耦。 + +坦白说,这对我来说听起来就像基本的礼貌——就像上完厕所后洗手,或者把垃圾扔进垃圾桶而不是丢在地上。事实上,在代码中留下混乱的行为应该像*乱扔垃圾*一样在社会上不被接受。这应该是*不应该做的事情*。 + +但这还不止于此。关心自己的代码是一回事,关心团队的代码则是另一回事。团队互相帮助,互相清理。他们遵循童子军规则,因为这不仅对自己有好处,对每个人都有好处。 + +作者:[鲍勃大叔](http://programmer.97things.oreilly.com/wiki/index.php/Uncle_Bob) \ No newline at end of file diff --git a/zh/thing_09/README.md b/zh/thing_09/README.md new file mode 100644 index 00000000..812377a2 --- /dev/null +++ b/zh/thing_09/README.md @@ -0,0 +1,21 @@ +# 在责怪他人之前,先检查你的代码 + +开发者们——我们所有人!——常常难以相信自己的代码有问题。这太不可能了,这一次一定是编译器出了问题。 + +然而,事实上,代码因为编译器、解释器、操作系统、应用服务器、数据库、内存管理器或其他系统软件的 bug 而出问题的情况非常(非常)罕见。是的,这些 bug 确实存在,但它们远比我们想象的要少得多。 + +我曾经确实遇到过编译器优化掉循环变量的 bug,但更多时候是我误以为编译器或操作系统有 bug。在这个过程中,我浪费了大量的时间、支持时间和管理时间,结果每次发现其实是我自己的错误时,都感到有点愚蠢。 + +如果这些工具被广泛使用、成熟且应用于各种技术栈中,那么就没有太多理由怀疑它们的质量。当然,如果工具是早期版本,或者全球只有少数人在使用,或者是一个很少被下载的 0.1 版本的开源软件,那么怀疑软件有问题可能是合理的。(同样,商业软件的 alpha 版本也可能值得怀疑。) + +鉴于编译器 bug 的罕见性,你最好将时间和精力投入到寻找代码中的错误,而不是证明编译器有问题。所有常见的调试建议都适用,因此要隔离问题、剔除调用、用测试包围它;检查调用约定、共享库和版本号;向他人解释问题;注意栈损坏和变量类型不匹配;在不同的机器和不同的构建配置(如调试和发布)上尝试代码。 + +质疑你自己和他人的假设。来自不同供应商的工具可能有不同的内置假设——同一供应商的不同工具也可能如此。当其他人报告一个你无法复现的问题时,去看看他们在做什么。他们可能在做一些你从未想到的事情,或者以不同的顺序做某事。 + +作为一个个人规则,如果我有一个无法定位的 bug,并且我开始认为是编译器的问题,那么是时候检查栈损坏了。如果添加跟踪代码使问题发生变化,这一点尤其正确。 + +多线程问题是另一个让人抓狂的 bug 来源。当系统是多线程时,所有倾向于简单代码的建议都被放大了。调试和单元测试不能指望一致地发现此类 bug,因此设计的简单性至关重要。 + +所以,在你急于责怪编译器之前,记住夏洛克·福尔摩斯的建议:“当你排除了所有不可能的情况后,剩下的无论多么不可思议,都一定是真相。” 并优先考虑它,而不是德克·根特的建议:“当你排除了所有不可思议的情况后,剩下的无论多么不可能,都一定是真相。” + +作者:[Allan Kelly](http://programmer.97things.oreilly.com/wiki/index.php/Allan_Kelly) \ No newline at end of file diff --git a/zh/thing_10/README.md b/zh/thing_10/README.md new file mode 100644 index 00000000..96c2031f --- /dev/null +++ b/zh/thing_10/README.md @@ -0,0 +1,29 @@ +# 谨慎选择你的工具 + +现代应用程序很少从零开始构建。它们通常使用现有的工具——组件、库和框架——进行组装,这有以下几个很好的理由: + +- 应用程序的规模、复杂性和精细度不断增加,而开发时间却越来越短。如果开发人员能够专注于编写更多的业务领域代码,而不是基础设施代码,那么他们的时间和智慧将得到更好的利用。 + +- 广泛使用的组件和框架可能比内部开发的组件和框架有更少的错误。 + +- 网上有很多高质量的免费软件,这意味着更低的开发成本,并且更容易找到具有必要兴趣和专业知识的开发人员。 + +- 软件的生产和维护是人力密集型的工作,因此购买可能比构建更便宜。 + +然而,为你的应用程序选择合适的工具组合可能是一个需要深思熟虑的棘手问题。事实上,在选择工具时,你应该记住以下几点: + +- 不同的工具可能依赖于不同的上下文假设——例如,周围的基础设施、控制模型、数据模型、通信协议等——这可能导致应用程序与工具之间的架构不匹配。这种不匹配会导致代码变得比必要的更复杂,因为需要各种变通和修补。 + +- 不同的工具有不同的生命周期,升级其中一个工具可能变得极其困难和耗时,因为新功能、设计变更甚至错误修复可能会导致与其他工具的不兼容。工具越多,问题可能越严重。 + +- 有些工具需要相当多的配置,通常通过一个或多个XML文件进行,这些文件可能会迅速失控。应用程序最终可能看起来像是全部用XML编写的,再加上一些编程语言中的几行代码。配置的复杂性将使应用程序难以维护和扩展。 + +- 当代码严重依赖于特定供应商的产品时,可能会在多个方面受到限制:可维护性、性能、演进能力、价格等,这就是所谓的供应商锁定。 + +- 如果你计划使用免费软件,你可能会发现它并不完全免费。你可能需要购买商业支持,而这并不一定便宜。 + +- 许可条款很重要,即使是免费软件。例如,在某些公司中,使用GNU许可条款下的软件是不可接受的,因为它的“病毒性”——即使用它开发的软件必须与其源代码一起分发。 + +我个人缓解这些问题的策略是从小处着手,只使用绝对必要的工具。通常,最初的焦点是消除进行低级基础设施编程(和问题)的需要,例如,通过使用一些中间件而不是使用原始套接字来开发分布式应用程序。然后根据需要添加更多工具。我还倾向于通过接口和分层将外部工具与我的业务领域对象隔离开来,这样如果我必须更换工具,只需付出很小的代价。这种方法的一个积极副作用是,我通常最终会得到一个比最初预测使用更少外部工具的更小的应用程序。 + +作者:[Giovanni Asproni](http://programmer.97things.oreilly.com/wiki/index.php/Giovanni_Asproni) \ No newline at end of file diff --git a/zh/thing_11/README.md b/zh/thing_11/README.md new file mode 100644 index 00000000..393afe27 --- /dev/null +++ b/zh/thing_11/README.md @@ -0,0 +1,40 @@ +# 使用领域语言编写代码 + +想象一下两个代码库。在其中一个代码库中,你遇到了以下代码: + +``` +if (portfolioIdsByTraderId.get(trader.getId()) + .containsKey(portfolio.getId())) {...} +``` + +你挠了挠头,想知道这段代码可能是用来做什么的。它似乎是从一个交易员对象中获取一个ID,然后用这个ID从一个看起来像是“映射的映射”中获取另一个映射,接着检查一个投资组合对象中的ID是否存在于这个内部映射中。你再次挠了挠头。你查找了`portfolioIdsByTraderId`的声明,发现了这个: + +``` +Map> portfolioIdsByTraderId; +``` + +渐渐地,你意识到这可能与某个交易员是否有权访问某个特定的投资组合有关。当然,你会在任何需要判断交易员是否有权访问某个投资组合的地方找到相同的查找片段——或者更可能是类似但略有不同的代码片段。 + +在另一个代码库中,你遇到了这段代码: + +``` +if (trader.canView(portfolio)) {...} +``` + +不需要挠头。你不需要知道交易员是如何知道的。也许在某个地方藏着一个这样的“映射的映射”。但那是交易员的事情,不是你的事情。 + +现在,你更愿意在哪个代码库中工作呢? + +曾经,我们只有非常基础的数据结构:位、字节和字符(实际上只是字节,但我们假装它们是字母和标点符号)。十进制数有点棘手,因为我们的十进制数字在二进制中表现不佳,所以我们有几种不同大小的浮点类型。然后是数组和字符串(实际上只是不同类型的数组)。接着我们有了栈、队列、哈希表、链表、跳表以及许多其他令人兴奋的数据结构*这些在现实世界中并不存在*。“计算机科学”就是花大量精力将现实世界映射到我们有限的数据结构中。真正的专家甚至能记住他们是如何做到的。 + +然后我们有了用户定义的类型!好吧,这并不是什么新鲜事,但它确实在某种程度上改变了游戏规则。如果你的领域中包含像交易员和投资组合这样的概念,你可以用名为`Trader`和`Portfolio`的类型来建模。但更重要的是,你也可以使用领域术语来建模*它们之间的关系*。 + +如果你不使用领域术语编写代码,你就是在创建一个隐含的(或者说秘密的)理解:*这个*`int`表示识别交易员的方式,而*那个*`int`表示识别投资组合的方式。(最好不要把它们搞混!)如果你用一个算法片段(比如一个键映射中的存在关系)来表示一个业务概念(“某些交易员不允许查看某些投资组合——这是非法的”),你并没有为审计和合规团队提供任何便利。 + +下一个程序员可能并不知道这个秘密,所以为什么不把它明确化呢?使用一个键来查找另一个键以执行存在性检查并不是非常直观。别人怎么能直觉地认为这就是防止利益冲突的业务规则的实现之处呢? + +在代码中明确表达领域概念意味着其他程序员可以更容易地理解代码的*意图*,而不必试图将算法与他们所理解的领域知识进行匹配。这也意味着当领域模型演化时——随着你对领域的理解加深,它必然会演化——你处于一个有利的位置来演化代码。结合良好的封装,规则很可能只存在于一个地方,并且你可以在不影响依赖代码的情况下进行更改。 + +几个月后接手这段代码的程序员会感谢你。几个月后接手这段代码的程序员可能就是你。 + +作者:[Dan North](http://programmer.97things.oreilly.com/wiki/index.php/Dan_North) \ No newline at end of file diff --git a/zh/thing_12/README.md b/zh/thing_12/README.md new file mode 100644 index 00000000..a2b05f9b --- /dev/null +++ b/zh/thing_12/README.md @@ -0,0 +1,17 @@ +# 代码即设计 + +想象一下,明天醒来,你得知建筑行业取得了世纪性的突破。数百万个廉价、速度惊人的机器人可以从无到有地制造材料,几乎不需要任何能源成本,并且能够自我修复。更棒的是:只要给这些机器人一个明确的建筑项目蓝图,它们就能在无需人类干预的情况下完成建造,且成本几乎可以忽略不计。 + +人们可以想象这对建筑行业的影响,但上游会发生什么变化呢?如果建筑成本可以忽略不计,建筑师和设计师的行为会发生怎样的改变?如今,在投资建设之前,物理模型和计算机模型会被构建并进行严格的测试。如果建筑成本几乎为零,我们还会费心去做这些测试吗?如果设计出了问题,没关系——找出问题所在,然后让我们的神奇机器人再建一个就是了。这还带来了更深层次的影响。随着模型的过时,未完成的设计会通过反复建造和改进来逐步接近最终目标。一个不经意的观察者可能很难区分未完成的设计和最终产品。 + +我们预测时间线的能力将逐渐消失。建筑成本比设计成本更容易计算——我们知道安装一根梁的大致成本,也知道需要多少根梁。随着可预测的任务成本趋近于零,不可预测的设计时间开始占据主导地位。结果虽然能更快地产生,但可靠的时间线却消失了。 + +当然,竞争经济的压力依然存在。随着建筑成本的消失,能够快速完成设计的公司将在市场上占据优势。快速完成设计成为工程公司的核心任务。不可避免地,某些对设计并不十分熟悉的人会看到一个未经验证的版本,看到提前发布的市场优势,然后说:“这看起来已经够好了。” + +一些生死攸关的项目可能会更加谨慎,但在许多情况下,消费者学会了忍受不完整的设计。公司总是可以派出我们的神奇机器人去“修补”他们出售的破损建筑和车辆。所有这些都指向了一个令人惊讶且反直觉的结论:我们唯一的前提是建筑成本的大幅降低,结果却是*质量变得更差*。 + +我们不应该对上述故事在软件领域上演感到惊讶。如果我们接受代码即设计——一个创造性的过程而非机械的过程——那么*软件危机*就得到了解释。我们现在面临的是*设计危机*:对高质量、经过验证的设计的需求超过了我们创造它们的能力。使用不完整设计的压力很大。 + +幸运的是,这个模型也为我们提供了如何改进的线索。物理模拟等同于自动化测试;软件设计在通过一系列严酷的测试验证之前,都不算完成。为了使这些测试更有效,我们正在寻找方法来控制大型系统的巨大状态空间。改进的语言和设计实践给了我们希望。最后,有一个无法回避的事实:伟大的设计是由致力于掌握其技艺的伟大设计师创造的。代码也不例外。 + +作者:[Ryan Brush](http://programmer.97things.oreilly.com/wiki/index.php/Ryan_Brush) \ No newline at end of file diff --git a/zh/thing_13/README.md b/zh/thing_13/README.md new file mode 100644 index 00000000..d3e2cc31 --- /dev/null +++ b/zh/thing_13/README.md @@ -0,0 +1,15 @@ +# 代码布局的重要性 + +许多年前,我曾在一个Cobol系统上工作,那里的员工不允许更改缩进,除非他们已经有理由修改代码,因为曾经有人因为让一行代码滑入行首的特殊列而破坏了某些东西。即使布局有时会误导人,我们也必须非常仔细地阅读代码,因为我们不能信任它。这个政策一定在程序员效率上付出了巨大的代价。 + +有研究表明,我们在编程时花费更多的时间在导航和阅读代码上——找到*在哪里*进行修改——而不是实际打字,所以这是我们想要优化的地方。 + +- *易于扫描。* 人们非常擅长视觉模式匹配(这是我们必须在草原上发现狮子时的遗留技能),所以我可以帮助自己,通过标准化所有与领域无关的内容,所有大多数商业语言带来的“偶然复杂性”,使其淡入背景。如果行为相同的代码看起来相同,那么我的感知系统将帮助我挑出差异。这就是为什么我也遵守关于如何在编译单元内布局类的各个部分的约定:常量、字段、公共方法、私有方法。 + +- *表达性布局。* 我们都学会了花时间找到正确的名称,以便我们的代码尽可能清楚地表达它的功能,而不仅仅是列出步骤——对吧?代码的布局也是这种表达性的一部分。第一步是让团队就基本的自动格式化器达成一致,然后我可能会在编码时手动进行调整。除非有积极的异议,团队会迅速收敛到一个共同的“手工完成”风格。格式化器无法理解我的意图(我应该知道,我曾经写过一个),对我来说更重要的是,换行和分组反映代码的意图,而不仅仅是语言的语法。(Kevin McGuire让我从自动代码格式化器的束缚中解脱出来。) + +- *紧凑的格式。* 我能在屏幕上看到的内容越多,我就能在不通过滚动或切换文件来打破上下文的情况下看到更多内容,这意味着我可以在脑海中保持更少的状态。对于8字符名称和行式打印机来说,冗长的过程注释和大量的空白是有意义的,但现在我生活在一个具有语法着色和交叉链接的IDE中。像素是我的限制因素,所以我希望每一个像素都能帮助我理解代码。我希望布局能帮助我理解代码,但仅此而已。 + +一位非程序员朋友曾经评论说,代码看起来像诗歌。我从真正优秀的代码中得到了这种感觉,文本中的每一部分都有其目的,并且它在那里帮助我理解这个想法。不幸的是,编写代码并没有像写诗那样浪漫的形象。 + +作者:[Steve Freeman](http://programmer.97things.oreilly.com/wiki/index.php/Steve_Freeman) \ No newline at end of file diff --git a/zh/thing_14/README.md b/zh/thing_14/README.md new file mode 100644 index 00000000..cc01f15d --- /dev/null +++ b/zh/thing_14/README.md @@ -0,0 +1,15 @@ +# 代码审查 + +你应该进行代码审查。为什么?因为它们*提高代码质量*并*降低缺陷率*。但原因可能并非你所想的那样。 + +由于之前可能有过一些不愉快的审查经历,许多程序员往往不喜欢代码审查。我曾见过一些组织要求所有代码在部署到生产环境之前必须通过正式审查。通常是由架构师或首席开发人员进行审查,这种做法可以描述为*架构师审查一切*。这在他们软件开发过程手册中有明确规定,因此程序员必须遵守。可能有些组织需要如此严格和正式的过程,但大多数组织并不需要。在大多数组织中,这种方法会适得其反。被审查者可能会觉得自己在被假释委员会审判。审查者既需要时间阅读代码,又需要时间跟上系统的所有细节。审查者很快就会成为这个过程中的瓶颈,过程也会迅速恶化。 + +代码审查的目的不应仅仅是纠正代码中的错误,而应是*分享知识*并建立共同的编码准则。与其他程序员分享你的代码可以实现集体代码所有权。让一个随机的团队成员与团队其他成员一起*走查代码*。与其寻找错误,你应该通过尝试学习和理解代码来进行审查。 + +在代码审查中要温和。确保评论是*建设性的,而不是刻薄的*。在审查会议中引入不同的*审查角色*,以避免团队成员之间的组织层级影响代码审查。例如,可以让一个审查者专注于文档,另一个专注于异常处理,第三个则关注功能实现。这种方法有助于将审查负担分散到团队成员之间。 + +每周安排一个固定的*代码审查日*。在审查会议上花费几个小时。每次会议以简单的轮换方式更换被审查者。记得每次审查会议也要在团队成员之间切换角色。*让新手参与*代码审查。他们可能经验不足,但他们新鲜的大学知识可以提供不同的视角。*让专家参与*,因为他们有经验和知识。他们会更快、更准确地识别出容易出错的代码。如果团队有由工具检查的*编码规范*,代码审查会更加顺利。这样,代码格式问题就不会在代码审查会议上讨论。 + +*让代码审查变得有趣*可能是成功的最重要因素。审查是关于审查的人。如果审查会议痛苦或乏味,就很难激励任何人。让它成为一个*非正式的代码审查*,其主要目的是在团队成员之间分享知识。把讽刺的评论留在外面,带上一块蛋糕或自带午餐吧。 + +作者:[Mattias Karlsson](http://programmer.97things.oreilly.com/wiki/index.php/Mattias_Karlsson) \ No newline at end of file diff --git a/zh/thing_15/README.md b/zh/thing_15/README.md new file mode 100644 index 00000000..be9fc950 --- /dev/null +++ b/zh/thing_15/README.md @@ -0,0 +1,25 @@ +# 用理性编码 + +尝试手动推理软件的正确性会导致形式化证明比代码本身更长,并且比代码更容易包含错误。自动化工具是更好的选择,但并不总是可行的。以下描述了一种中间路径:半正式地推理正确性。 + +基本方法是将所有考虑的代码划分为短小的部分——从单行代码(如函数调用)到少于十行的代码块——并论证它们的正确性。这些论证只需要足够强有力,以说服你那位持怀疑态度的同行程序员。 + +选择的部分应确保在每个端点处,*程序的状态*(即程序计数器和所有“存活”对象的值)满足一个易于描述的属性,并且该部分的功能(状态转换)易于描述为单一任务——这将使推理更简单。这些端点属性概括了诸如函数的*前置条件*和*后置条件*,以及循环和类(相对于它们的实例)的*不变量*等概念。努力使各部分尽可能相互独立,可以简化推理,并且在修改这些部分时是必不可少的。 + +许多众所周知的(尽管可能不太被遵循)被认为是“良好”的编码实践使推理更容易。因此,仅仅是有意推理你的代码,你就已经开始朝着更好的风格和结构思考了。不出所料,这些实践中的大多数可以通过静态代码分析器进行检查: + +- 避免使用 `goto` 语句,因为它们会使远程部分高度相互依赖。 +- 避免使用可修改的全局变量,因为它们会使所有使用它们的部分相互依赖。 +- 每个变量的作用域应尽可能小。例如,可以在首次使用前声明局部对象。 +- 在适当的情况下,使对象*不可变*。 +- 通过使用水平和垂直间距使代码可读。例如,对齐相关结构并使用空行分隔两个部分。 +- 通过为对象、类型、函数等选择描述性(但相对较短)的名称,使代码自文档化。 +- 如果需要嵌套部分,将其作为一个函数。 +- 使你的函数简短并专注于单一任务。旧的*24行限制*仍然适用。尽管屏幕尺寸和分辨率已经改变,但自20世纪60年代以来,人类的认知能力并没有变化。 +- 函数的参数应尽可能少(四个是一个很好的上限)。这并不限制传递给函数的数据:将相关参数分组到一个对象中,可以利用*对象不变量*并节省推理,例如它们的一致性和连贯性。 +- 更一般地说,每个代码单元,从代码块到库,都应该有一个*狭窄的接口*。较少的通信减少了所需的推理。这意味着返回内部状态的*getter*是一种负担——不要向对象请求信息来工作。相反,要求对象使用它已有的信息来完成工作。换句话说,*封装*就是——也仅仅是——关于*狭窄的接口*。 +- 为了保持类的*不变量*,应避免使用*setter*,因为*setter*往往允许破坏控制对象状态的不变量。 + +除了推理其正确性外,论证你的代码还能让你更好地理解它。将你获得的见解传达给每个人,以造福所有人。 + +作者:[Yechiel Kimchi](http://programmer.97things.oreilly.com/wiki/index.php/Yechiel_Kimchi) \ No newline at end of file diff --git a/zh/thing_16/README.md b/zh/thing_16/README.md new file mode 100644 index 00000000..d0561056 --- /dev/null +++ b/zh/thing_16/README.md @@ -0,0 +1,15 @@ +# 关于注释的评论 + +在我大学的第一堂编程课上,老师发了两张BASIC编程纸。黑板上写着作业:“编写一个程序,输入并计算10个保龄球得分的平均值。”然后老师就离开了教室。这能有多难呢?我不记得我最终的解决方案是什么,但我确信其中有一个`FOR/NEXT`循环,整个程序不会超过15行。编程纸——对于正在阅读这篇文章的年轻人来说,是的,我们曾经在将代码输入计算机之前手写代码——每张纸大约可以写70行代码。我非常困惑为什么老师会给我们两张纸。由于我的字迹一直很糟糕,我用第二张纸非常工整地重新抄写了我的代码,希望能因为风格而获得一些额外的分数。 + +令我非常惊讶的是,当我在下一节课开始时收到作业时,我只得到了一个勉强及格的分数。(这预示着我大学剩余时间的命运。)在我工整抄写的代码顶部潦草地写着:“没有注释?” + +仅仅老师和我知道程序应该做什么是不够的。作业的一部分目的是教会我,我的代码应该向后续的程序员解释自己。这是一个我从未忘记的教训。 + +注释并不是邪恶的。它们与基本的分支或循环结构一样,是编程中不可或缺的。大多数现代语言都有类似于javadoc的工具,可以解析格式正确的注释以自动构建API文档。这是一个非常好的开始,但还远远不够。在你的代码中应该包含关于代码应该做什么的解释。遵循古老的格言“如果写起来很难,那么读起来也应该很难”来编写代码,对你的客户、雇主、同事和未来的自己都是一种伤害。 + +另一方面,你可能会在注释上做得过头。确保你的注释能够澄清你的代码,而不是掩盖它。在代码中散布相关的注释,解释代码应该完成什么。你的头部注释应该给任何程序员足够的信息来使用你的代码,而不需要阅读它,而你的行内注释应该帮助下一个开发者修复或扩展它。 + +在一次工作中,我不同意上级做出的设计决策。像年轻程序员常做的那样,我感到有些刻薄,于是将指示我使用他们设计的电子邮件文本粘贴到文件的头部注释块中。结果发现,这家公司的经理们实际上在代码提交时会审查代码。这是我第一次接触到“职业限制行为”这个术语。 + +作者:[Cal Evans](http://programmer.97things.oreilly.com/wiki/index.php/Cal_Evans) \ No newline at end of file diff --git a/zh/thing_17/README.md b/zh/thing_17/README.md new file mode 100644 index 00000000..68df95ec --- /dev/null +++ b/zh/thing_17/README.md @@ -0,0 +1,13 @@ +# 只注释代码无法表达的内容 + +理论与实践之间的差距在实践中比在理论上更大——这一观察当然适用于注释。理论上,注释代码的总体想法听起来是值得的:为读者提供细节,解释正在发生的事情。还有什么比提供帮助更有帮助的呢?然而,在实践中,注释往往成为一种负担。与任何其他形式的写作一样,写好注释需要技巧。大部分技巧在于知道何时不写注释。 + +当代码格式不正确时,编译器、解释器和其他工具肯定会提出异议。如果代码在功能上存在某种错误,代码审查、静态分析、测试和生产环境中的日常使用会暴露出大多数错误。但注释呢?在《编程风格的元素》中,Kernighan 和 Plauger 指出:“如果注释是错误的,那么它的价值为零(或为负)。”然而,这样的注释常常在代码库中泛滥并存活下来,而代码错误却永远不会如此。它们提供了持续的干扰和错误信息,对程序员的思维产生了微妙但持续的拖累。 + +那些在技术上没有错误,但对代码没有增加任何价值的注释呢?这样的注释就是噪音。那些重复代码的注释并没有为读者提供额外的信息——在代码中陈述一次,再用自然语言陈述一次,并不会使它更真实或更现实。被注释掉的代码不是可执行代码,因此它对读者或运行时都没有任何有用的效果。它也会很快变得过时。与版本相关的注释和被注释掉的代码试图解决版本控制和历史记录的问题。这些问题已经被版本控制工具(更有效地)解决了。 + +代码库中充斥着噪音注释和错误注释,这鼓励程序员忽略所有注释,要么跳过它们,要么采取积极措施隐藏它们。程序员是足智多谋的,他们会绕过任何被认为是损害的东西:折叠注释;切换配色方案,使注释和背景颜色相同;编写脚本来过滤掉注释。为了避免程序员聪明才智的误用,并减少忽略任何真正有价值的注释的风险,注释应该被视为代码的一部分。每个注释都应该为读者增加一些价值,否则就应该删除或重写这些浪费的注释。 + +那么什么才算是价值呢?注释应该表达代码无法表达的内容。解释一段代码应该已经表达的内容的注释,是在邀请改变代码结构或编码约定,使代码自己说话。与其为糟糕的方法或类名进行补偿,不如重命名它们。与其在长函数中注释部分代码,不如提取出较小的函数,其名称能够捕捉到前几部分的意图。尽量通过代码表达尽可能多的内容。你在代码中可以表达的内容与你希望表达的内容之间的任何差距,都可能成为有用注释的候选。注释代码无法表达的内容,而不仅仅是它没有表达的内容。 + +作者:[Kevlin Henney](http://programmer.97things.oreilly.com/wiki/index.php/Kevlin_Henney) \ No newline at end of file diff --git a/zh/thing_18/README.md b/zh/thing_18/README.md new file mode 100644 index 00000000..00f342a2 --- /dev/null +++ b/zh/thing_18/README.md @@ -0,0 +1,28 @@ +# 持续学习 + +我们生活在一个有趣的时代。随着开发工作分布到全球各地,你会发现有很多人能够胜任你的工作。你需要不断学习以保持竞争力。否则,你将会像恐龙一样,困在同一个岗位上,直到有一天你不再被需要,或者你的工作被外包给更廉价的资源。 + +那么你该如何应对呢?一些雇主非常慷慨,会提供培训来扩展你的技能。而另一些雇主可能无法抽出时间或资金进行任何培训。为了保险起见,你需要为自己的教育负责。 + +以下是一些让你持续学习的方法。其中许多方法可以在互联网上免费找到: + +- 阅读书籍、杂志、博客、Twitter 动态和网站。如果你想深入了解某个主题,可以考虑加入邮件列表或新闻组。 +- 如果你真的想沉浸在一项技术中,那就动手实践——写一些代码。 +- 尽量与导师一起工作,因为成为顶尖人物可能会阻碍你的学习。虽然你可以从任何人身上学到东西,但从比你更聪明或更有经验的人那里,你可以学到更多。如果你找不到导师,考虑换个环境。 +- 使用虚拟导师。在网上找到你真正喜欢的作者和开发者,阅读他们写的所有内容。订阅他们的博客。 +- 了解你使用的框架和库。了解某物的工作原理会让你更好地使用它。如果它们是开源的,那你就真的很幸运了。使用调试器逐步查看代码,了解其内部运作。你将看到一些非常聪明的人编写和审查的代码。 +- 每当你犯错、修复错误或遇到问题时,试着真正理解发生了什么。很可能其他人也遇到了同样的问题,并在网上某个地方发布了相关信息。Google 在这里非常有用。 +- 学习某样东西的一个非常好的方法是教授或谈论它。当人们要听你讲并问你问题时,你会非常有动力去学习。可以尝试在工作中的午餐学习会、用户组或本地会议上进行分享。 +- 加入或创建一个学习小组(类似于模式社区)或本地用户组,专注于你感兴趣的语言、技术或领域。 +- 参加技术会议。如果你无法参加,许多会议会将他们的演讲免费发布在网上。 +- 通勤时间长?听听播客。 +- 你是否曾经在代码库上运行静态分析工具或查看 IDE 中的警告?理解它们报告的内容及其原因。 +- 遵循《程序员修炼之道》的建议,每年学习一门新语言。至少学习一项新技术或工具。扩展视野可以为你当前的技术栈带来新的想法。 +- 你学习的内容不必都与技术相关。了解你工作的领域,以便更好地理解需求并帮助解决业务问题。学习如何提高生产力——如何更好地工作——也是一个不错的选择。 +- 回到学校学习。 + +如果我们能像《黑客帝国》中的尼奥那样,直接将所需的信息下载到大脑中,那该多好。但我们没有这种能力,所以学习需要时间投入。你不必每时每刻都在学习。每周花一点时间学习,总比什么都不做要好。工作之外还有(或应该有)生活。 + +技术变化很快。不要被落下。 + +作者:[Clint Shank](http://programmer.97things.oreilly.com/wiki/index.php/Clint_Shank) \ No newline at end of file diff --git a/zh/thing_19/README.md b/zh/thing_19/README.md new file mode 100644 index 00000000..3a6ef68e --- /dev/null +++ b/zh/thing_19/README.md @@ -0,0 +1,21 @@ +# 便利性不是一种“能力” + +关于设计良好API的重要性和挑战,已经有很多讨论。第一次就设计正确是很困难的,之后要改变则更加困难。这有点像抚养孩子。大多数有经验的程序员都明白,一个好的API应该遵循一致的抽象层次,展现出一致性和对称性,并形成一个富有表现力的语言的词汇表。然而,了解指导原则并不自动转化为适当的行为。吃甜食对你有害。 + +与其高高在上地说教,我想针对一个特定的API设计“策略”进行批评,这是我一次又一次遇到的:便利性的论点。它通常始于以下“洞察”之一: + +- 我不希望其他类必须进行两次单独的调用来完成这一件事。 +- 如果这个方法几乎和另一个方法一样,为什么我还要再写一个方法呢?我只需要加一个简单的开关。 +- 看,这很简单:如果第二个字符串参数以“.txt”结尾,方法会自动假设第一个参数是文件名,所以我真的不需要两个方法。 + +尽管初衷良好,这样的论点往往会降低使用API的代码的可读性。像这样的方法调用: + +``` +parser.processNodes(text, false); +``` + +如果不了解实现或至少查阅文档,几乎是没有意义的。这种方法可能是为了方便实现者而设计的,而不是为了方便调用者——“我不希望调用者必须进行两次单独的调用”转化为“我不想编写两个单独的方法”。如果便利性是为了解决繁琐、笨拙或尴尬的问题,那么它本身并没有根本性的错误。然而,如果我们更仔细地思考一下,解决这些症状的方法是效率、一致性和优雅性,而不一定是便利性。API应该隐藏底层的复杂性,因此我们可以合理地期望良好的API设计需要一些努力。一个单一的大型方法肯定比一组经过深思熟虑的操作更方便编写,但它会更容易使用吗? + +将API比作语言的隐喻可以引导我们在这些情况下做出更好的设计决策。API应该提供一种富有表现力的语言,为上层的调用者提供足够的词汇来提出和回答有用的问题。这并不意味着它应该为每个可能值得提出的问题提供一个方法或动词。丰富的词汇表使我们能够表达意义的细微差别。例如,我们更喜欢说“跑”而不是“走(true)”,尽管它可以被视为本质上相同的操作,只是以不同的速度执行。一个一致且经过深思熟虑的API词汇表可以使上层的代码更具表现力且易于理解。更重要的是,一个可组合的词汇表允许其他程序员以你可能没有预料到的方式使用API——这对API的用户来说确实是一个巨大的便利!下次你忍不住想把几件事合并到一个API方法中时,请记住,英语中没有`MakeUpYourRoomBeQuietAndDoYourHomeWork`这样的单词,尽管对于这样一个经常被请求的操作来说,它似乎非常方便。 + +作者:[Gregor Hohpe](http://programmer.97things.oreilly.com/wiki/index.php/Gregor_Hohpe) \ No newline at end of file diff --git a/zh/thing_20/README.md b/zh/thing_20/README.md new file mode 100644 index 00000000..7b2a9ae5 --- /dev/null +++ b/zh/thing_20/README.md @@ -0,0 +1,15 @@ +# 尽早且频繁地部署 + +调试部署和安装过程通常被推迟到项目接近尾声时进行。在一些项目中,编写安装工具的任务被委派给发布工程师,他们将其视为“必要的恶”。评审和演示通常在一个手工打造的环境中进行,以确保一切正常。结果是,团队在可能为时已晚时才获得部署过程或部署环境的经验,无法进行更改。 + +安装/部署过程是客户首先看到的内容,而一个简单的安装/部署过程是拥有可靠(或至少易于调试)生产环境的第一步。部署的软件是客户将要使用的。如果不确保部署正确设置应用程序,你会在客户彻底使用你的软件之前就引发他们的疑问。 + +在项目开始时就开始安装过程,将给你时间在产品开发周期中逐步改进这个过程,并有机会对应用程序代码进行更改,以使安装更容易。定期在干净的环境中运行和测试安装过程还可以检查你是否在代码中做出了依赖于开发或测试环境的假设。 + +将部署放在最后意味着部署过程可能需要更加复杂,以绕过代码中的假设。在集成开发环境(IDE)中看似很棒的想法,在那里你可以完全控制环境,可能会导致更复杂的部署过程。最好尽早了解所有的权衡。 + +虽然“能够部署”在早期似乎没有太多业务价值,相比于在开发者的笔记本电脑上看到应用程序运行,但简单的事实是,在你能够在目标环境中演示你的应用程序之前,还有很多工作要做才能交付业务价值。如果你推迟部署过程的理由是它很简单,那么无论如何都要做,因为它的成本很低。如果它太复杂,或者有太多的不确定性,就像处理应用程序代码一样:实验、评估,并在过程中重构部署过程。 + +安装/部署过程对你的客户或专业服务团队的生产力至关重要,因此你应该在过程中测试和重构这个过程。我们在整个项目中测试和重构源代码。部署过程同样值得这样做。 + +作者:[Steve Berczuk](http://programmer.97things.oreilly.com/wiki/index.php/Steve_Berczuk) \ No newline at end of file diff --git a/zh/thing_21/README.md b/zh/thing_21/README.md new file mode 100644 index 00000000..763ec0b9 --- /dev/null +++ b/zh/thing_21/README.md @@ -0,0 +1,17 @@ +# 区分业务异常与技术异常 + +在运行时,事情出错的原因基本上有两种:阻止我们使用应用程序的技术问题和阻止我们误用应用程序的业务逻辑。大多数现代语言,如LISP、Java、Smalltalk和C#,都使用异常来通知这两种情况。然而,这两种情况是如此不同,应该仔细区分开来。使用相同的异常层次结构(更不用说相同的异常类)来表示它们,可能会导致混淆。 + +当存在编程错误时,可能会出现无法解决的技术问题。例如,如果你尝试从大小为17的数组中访问第83个元素,那么程序显然偏离了轨道,应该抛出某种异常。更微妙的情况是使用不适当的参数调用某些库代码,导致库内部出现相同的情况。 + +试图解决你自己造成的这些情况是错误的。相反,我们让异常冒泡到最高的架构层次,并让一些通用的异常处理机制尽其所能确保系统处于安全状态,例如回滚事务、记录日志并通知管理员,以及(礼貌地)向用户报告。 + +这种情况的一个变体是当你处于“库情境”中,调用者破坏了你的方法契约,例如传递了一个完全奇怪的参数或没有正确设置依赖对象。这与从17个元素中访问第83个元素相当:调用者应该进行检查;不这样做是客户端程序员的错误。正确的响应是抛出一个技术异常。 + +另一种仍然是技术性的情况是,由于执行环境中的问题(例如无响应的数据库),程序无法继续执行。在这种情况下,你必须假设基础设施已经尽其所能解决问题——修复连接并重试合理的次数——但失败了。即使原因不同,调用代码的情况也是相似的:它对此无能为力。因此,我们通过一个异常来通知这种情况,并让它冒泡到通用的异常处理机制。 + +与这些情况相反,我们有一种情况是你由于领域逻辑原因无法完成调用。在这种情况下,我们遇到了一种异常情况,即不寻常且不希望的,但并不奇怪或程序上有错误。例如,如果我尝试从一个资金不足的账户中取款。换句话说,这种情况是契约的一部分,抛出异常只是模型中的一种*替代返回路径*,客户端应该意识到并准备好处理。对于这些情况,创建特定的异常或单独的异常层次结构是合适的,以便客户端可以根据自己的条件处理这种情况。 + +将技术异常和业务异常混合在同一个层次结构中会模糊区别,并使调用者混淆方法的契约是什么,调用前需要确保什么条件,以及应该处理什么情况。区分这些情况可以提供清晰性,并增加技术异常由某些应用程序框架处理的机会,而业务领域异常实际上由客户端代码考虑和处理。 + +作者:[Dan Bergh Johnsson](http://programmer.97things.oreilly.com/wiki/index.php/Dan_Bergh_Johnsson) \ No newline at end of file diff --git a/zh/thing_22/README.md b/zh/thing_22/README.md new file mode 100644 index 00000000..bafee396 --- /dev/null +++ b/zh/thing_22/README.md @@ -0,0 +1,25 @@ +# 进行大量的刻意练习 + +刻意练习不仅仅是执行一项任务。如果你问自己“我为什么要执行这项任务?”而你的回答是“为了完成任务”,那么你并没有在进行刻意练习。 + +你进行刻意练习是为了提高你执行任务的能力。它关乎技能和技巧。刻意练习意味着重复。它意味着以增加你对任务一个或多个方面的掌握为目标来执行任务。它意味着重复这些重复。慢慢地,一遍又一遍。直到你达到你想要的掌握水平。你进行刻意练习是为了掌握任务,而不是为了完成任务。 + +有偿开发的主要目标是完成一个产品,而刻意练习的主要目标是提高你的表现。它们并不相同。问问自己,你花了多少时间开发别人的产品?花了多少时间开发自己? + +获得专业知识需要多少刻意练习? + +- Peter Norvig [写道](http://norvig.com/21-days.html):“可能需要10,000小时[...]是那个神奇的数字。” +- 在《Leading Lean Software Development》中,Mary Poppendieck指出:“精英表演者至少需要10,000小时的刻意专注练习才能成为专家。” + +专业知识是随着时间的推移逐渐获得的——而不是在第10,000小时突然获得的!然而,10,000小时是一个很大的数字:大约每周20小时,持续10年。考虑到这种程度的投入,你可能会担心自己不是专家的料。你是的。伟大很大程度上是有意识的选择。*你的选择。*过去二十年的研究表明,获得专业知识的主要因素是花在刻意练习上的时间。天赋能力*不是*主要因素。 + +- Mary:“专家表现的研究者之间有一个广泛的共识,即天赋能力的作用不超过一个门槛;你必须具备最低限度的自然能力才能开始一项运动或职业。之后,那些表现出色的人是最努力的人。” + +刻意练习你已经擅长的东西几乎没有意义。刻意练习意味着练习你不擅长的东西。 + +- Peter:“[发展专业知识]的关键是*刻意*练习:不仅仅是反复做,而是挑战自己,尝试一个超出你当前能力的任务,在执行过程中和之后分析你的表现,并纠正任何错误。” +- Mary:“刻意练习并不意味着做你擅长的事情;它意味着挑战自己,做你不擅长的事情。所以它不一定是好玩的。” + +刻意练习关乎学习。关乎改变你的学习;改变你行为的学习。祝你好运。 + +作者:[Jon Jagger](http://programmer.97things.oreilly.com/wiki/index.php/Jon_Jagger) \ No newline at end of file diff --git a/zh/thing_23/README.md b/zh/thing_23/README.md new file mode 100644 index 00000000..74dbb073 --- /dev/null +++ b/zh/thing_23/README.md @@ -0,0 +1,15 @@ +# 领域特定语言 + +每当你聆听任何领域的专家讨论时,无论是国际象棋选手、幼儿园教师还是保险代理人,你都会注意到他们的词汇与日常语言大不相同。这就是领域特定语言(DSLs)的一部分:一个特定领域拥有专门的词汇来描述该领域特有的内容。 + +在软件世界中,DSLs 是关于用特定领域的语言编写的可执行表达式,这些语言具有有限的词汇和语法,且对领域专家来说是可读、可理解且——希望是——可编写的。针对软件开发人员或科学家的 DSLs 已经存在很长时间了。例如,配置文件中出现的 Unix“小语言”以及使用 LISP 宏创建的语言就是一些较早的例子。 + +DSLs 通常被分类为**内部**或**外部**: + +- **内部 DSLs** 是用通用编程语言编写的,其语法被调整得更像自然语言。这对于提供更多语法糖和格式化可能性的语言(例如 Ruby 和 Scala)来说比那些不提供的语言(例如 Java)更容易。大多数内部 DSLs 包装现有的 API、库或业务代码,并提供一个包装器以便更轻松地访问功能。它们只需运行即可直接执行。根据实现和领域的不同,它们用于构建数据结构、定义依赖关系、运行进程或任务、与其他系统通信或验证用户输入。内部 DSL 的语法受宿主语言的限制。有许多模式——例如表达式构建器、方法链和注解——可以帮助你将宿主语言调整为你的 DSL。如果宿主语言不需要重新编译,内部 DSL 可以与领域专家并肩工作,快速开发。 + +- **外部 DSLs** 是语言的文本或图形表达——尽管文本 DSLs 往往比图形 DSLs 更常见。文本表达式可以通过工具链处理,工具链包括词法分析器、解析器、模型转换器、生成器以及任何其他类型的后处理。外部 DSLs 大多被读入内部模型,这些模型构成了进一步处理的基础。定义语法(例如,在 EBNF 中)是有帮助的。语法为生成工具链的部分(例如编辑器、可视化器、解析器生成器)提供了起点。对于简单的 DSLs,手工制作的解析器可能就足够了——例如使用正则表达式。如果对自定义解析器要求过多,它们可能会变得难以管理,因此有必要查看专门用于处理语言语法和 DSLs 的工具——例如 openArchitectureWare、ANTlr、SableCC、AndroMDA。将外部 DSLs 定义为 XML 方言也很常见,尽管可读性通常是一个问题——特别是对于非技术读者。 + +你必须始终考虑 DSL 的目标受众。他们是开发人员、经理、业务客户还是最终用户?你必须根据目标受众调整语言的技术水平、可用工具、语法帮助(例如智能感知)、早期验证、可视化和表示。通过隐藏技术细节,DSLs 可以赋予用户能力,使他们能够根据需要调整系统,而无需开发人员的帮助。由于初始语言框架到位后工作的潜在分配,它还可以加快开发速度。语言可以逐步发展。现有表达式和语法也有不同的迁移路径。 + +作者:[Michael Hunger](http://programmer.97things.oreilly.com/wiki/index.php/Michael_Hunger) \ No newline at end of file diff --git a/zh/thing_24/README.md b/zh/thing_24/README.md new file mode 100644 index 00000000..80546259 --- /dev/null +++ b/zh/thing_24/README.md @@ -0,0 +1,13 @@ +# 不要害怕破坏事物 + +每个有行业经验的人都无疑曾在一个代码库岌岌可危的项目上工作过。系统设计得不好,改变一件事总是会破坏另一个不相关的功能。每当添加一个模块时,程序员的目标是尽可能少地改变,并在每次发布时屏住呼吸。这就像在摩天大楼里用工字梁玩积木游戏,注定会引发灾难。 + +之所以做出改变如此令人紧张,是因为系统生病了。它需要医生,否则它的状况只会恶化。你已经知道你的系统出了什么问题,但你害怕打破鸡蛋来做煎蛋卷。一位熟练的外科医生知道为了手术必须进行切割,但熟练的外科医生也知道这些切割是暂时的,会愈合。手术的最终结果值得最初的痛苦,病人应该会恢复到比手术前更好的状态。 + +不要害怕你的代码。在你移动东西时,如果某些东西暂时被破坏了,谁在乎呢?对变化的瘫痪性恐惧是你的项目陷入这种状态的原因。投入时间进行重构将在项目的生命周期中多次回报自己。一个额外的好处是,你的团队在处理病态系统方面的经验使你们都成为了知道它*应该*如何工作的专家。应用这些知识,而不是怨恨它。在一个你讨厌的系统上工作不是任何人应该花时间的方式。 + +重新定义内部接口,重构模块,重构复制粘贴的代码,并通过减少依赖来简化你的设计。你可以通过消除角落案例来显著降低代码复杂性,这些案例通常是由于功能耦合不当造成的。慢慢地将旧结构过渡到新结构,并在过程中进行测试。试图在“一次大动作”中完成大规模重构会导致足够多的问题,使你在中途考虑放弃整个努力。 + +成为不害怕切除病态部分以腾出愈合空间的外科医生。这种态度具有传染性,会激励其他人开始处理他们一直推迟的清理项目。保持一个“卫生”任务列表,团队认为这些任务对项目的总体利益是值得的。说服管理层,即使这些任务可能不会产生可见的结果,它们也会减少开支并加快未来的发布。永远不要停止关心代码的总体“健康”。 + +作者:[Mike Lewis](http://programmer.97things.oreilly.com/wiki/index.php/Mike_Lewis) \ No newline at end of file diff --git a/zh/thing_25/README.md b/zh/thing_25/README.md new file mode 100644 index 00000000..57f1fe06 --- /dev/null +++ b/zh/thing_25/README.md @@ -0,0 +1,26 @@ +# 不要在你的测试数据上耍小聪明 + +> *天色已晚。我正在插入一些占位数据来测试我一直在处理的页面布局。* + +> *我借用了The Clash乐队成员的名字作为用户名称。公司名称?用Sex Pistols乐队的歌曲标题就可以了。现在我需要一些股票代码——只是一些大写的四个字母的单词。* + +> *我用了**那些**四个字母的单词。* + +> *这似乎无害。只是为了自娱自乐,也许第二天在其他开发者连接真实数据源之前也能逗乐他们。* + +> *第二天早上,项目经理为演示截取了一些屏幕截图。** + +编程历史中充斥着这类战争故事。开发者和设计师做的“没人会看到”的事情,意外地变得可见。 +泄露的类型可能各不相同,但当它发生时,对责任人、团队或公司来说可能是致命的。例子包括: + +- 在一次状态会议上,客户点击了一个尚未实现的按钮。他们被告知:“别再点击那个了,你这个白痴。” +- 一个维护遗留系统的程序员被告知要添加一个错误对话框,并决定使用现有的幕后日志输出来驱动它。当某些东西崩溃时,用户突然看到诸如“Holy database commit failure, Batman!”这样的消息。 +- 有人混淆了测试和管理界面,并做了一些“有趣”的数据输入。顾客在你的在线商店中发现了一个价值100万美元的“比尔·盖茨形状的个人按摩器”。 + +借用那句老话“谎言可以绕地球半圈,而真相还在穿鞋”,在这个时代,一个失误可以在开发者所在时区的任何人醒来之前被Digg、Twitter和Flibflarb传播。 + +即使是你的源代码也不一定免于审查。2004年,当Windows 2000源代码的压缩包出现在文件共享网络上时,一些人愉快地通过grep搜索其中的脏话、侮辱和[其他有趣的内容](http://www.kuro5hin.org/story/2004/2/15/71552/7795)。(我承认,自从那时起,我时不时会借用注释`// TERRIBLE HORRIBLE NO GOOD VERY BAD HACK`!) + +总之,当你在代码中编写任何文本时——无论是注释、日志、对话框还是测试数据——始终要问自己,如果它公开了会是什么样子。这将避免大家尴尬。 + +作者:[Rod Begbie](http://programmer.97things.oreilly.com/wiki/index.php/Rod_Begbie) \ No newline at end of file diff --git a/zh/thing_26/README.md b/zh/thing_26/README.md new file mode 100644 index 00000000..5a3d23c0 --- /dev/null +++ b/zh/thing_26/README.md @@ -0,0 +1,47 @@ +# 不要忽视错误! + +> *一天晚上,我走在街上,准备去酒吧见一些朋友。我们有一段时间没有一起喝啤酒了,我期待着再次见到他们。匆忙中,我没有注意脚下的路。我被路边的路缘绊倒,脸朝下摔倒在地。好吧,我想这是我注意力不集中的报应。* + +> *我的腿受伤了,但我急着去见朋友。所以我爬起来继续走。随着我走得更远,疼痛越来越严重。虽然我一开始以为这只是惊吓,但我很快意识到有些不对劲。* + +> *尽管如此,我还是匆匆赶到了酒吧。到达时,我已经痛苦不堪。那晚我过得并不愉快,因为我被疼痛分散了注意力。第二天早上我去看了医生,发现我的胫骨骨折了。如果我在感到疼痛时停下来,我就不会因为继续行走而造成更多的伤害。这可能是我人生中最糟糕的“宿醉”了。* + +太多程序员编写的代码就像我那灾难性的夜晚一样。 + +*错误,什么错误?不会很严重的。真的。我可以忽略它。* 这并不是编写健壮代码的好策略。事实上,这只是纯粹的懒惰(错误的那种)。无论你认为代码中出现错误的可能性有多小,你都应该始终检查并处理它。每一次都是如此。如果你不这样做,你并不是在节省时间:你是在为未来积累潜在的问题。 + +我们在代码中报告错误的方式有多种,包括: + +- **返回码** 可以用作函数的返回值,表示“它没有成功”。错误返回码太容易被忽略了。你在代码中看不到任何突出显示问题的内容。事实上,忽略一些标准C函数的返回值已经成为标准做法。你有多经常检查`printf`的返回值? + +- **errno** 是C语言中一个奇怪的异常,它是一个单独的全局变量,用于表示错误。它容易被忽略,难以使用,并导致各种棘手的问题——例如,当你有多个线程调用同一个函数时会发生什么?有些平台在这里为你屏蔽了痛苦;而有些则没有。 + +- **异常** 是一种更结构化、语言支持的错误信号和处理方式。你不可能忽略它们。或者你能吗?我见过很多这样的代码: + +``` +try { + // ...做点什么... +} +catch (...) {} // 忽略错误 +``` + +这种糟糕的构造的唯一优点是,它突显了你正在做一些道德上可疑的事情。 + +如果你忽视错误,视而不见,假装什么都没出错,你将面临巨大的风险。就像我的腿最终比如果我立即停止行走时更糟糕一样,不管不顾地继续前进可能导致非常复杂的故障。尽早处理问题。保持简洁。 + +不处理错误会导致: + +- **脆弱的代码。** 充满令人兴奋、难以发现的错误的代码。 +- **不安全的代码。** 黑客经常利用糟糕的错误处理来入侵软件系统。 +- **糟糕的结构。** 如果你的代码中有错误,而这些错误需要不断处理,那么你可能有一个糟糕的接口。表达它,使错误不那么具有侵入性,处理起来也不那么繁琐。 + +正如你应该检查代码中的所有潜在错误一样,你需要在接口中暴露所有可能的错误条件。不要隐藏它们,假装你的服务总是能正常工作。 + +为什么我们不检查错误?有一些常见的借口。你同意哪些?你会如何反驳每一个? + +- 错误处理会扰乱代码的流程,使其更难阅读,更难发现“正常”的执行流程。 +- 这是额外的工作,而我的截止日期迫在眉睫。 +- 我知道这个函数调用**永远不会**返回错误(`printf`总是有效,`malloc`总是返回新内存——如果它失败了,我们有更大的问题……)。 +- 这只是一个玩具程序,不需要编写到生产级别。 + +作者:[Pete Goodliffe](http://programmer.97things.oreilly.com/wiki/index.php/Pete_Goodliffe) \ No newline at end of file diff --git a/zh/thing_27/README.md b/zh/thing_27/README.md new file mode 100644 index 00000000..01c70704 --- /dev/null +++ b/zh/thing_27/README.md @@ -0,0 +1,13 @@ +# 不要只学习语言,要理解其文化 + +在高中时,我必须学习一门外语。当时我认为只要英语好就够了,所以我选择在法语课上睡了三年。几年后,我去突尼斯度假。阿拉伯语是那里的官方语言,由于突尼斯曾是法国殖民地,法语也被广泛使用。英语只在旅游区通行。由于我对语言的忽视,我发现自己只能待在泳池边读詹姆斯·乔伊斯的《芬尼根的守灵夜》,这本书在形式和语言上都是一部杰作。乔伊斯巧妙地融合了四十多种语言,虽然令人疲惫,但也让我感到惊讶。意识到外语词汇和短语如何交织在一起,为作者提供了新的表达方式,这一点在我的编程生涯中一直伴随着我。 + +在他们具有开创性的著作《程序员修炼之道》中,Andy Hunt 和 Dave Thomas 鼓励我们每年学习一门新的编程语言。我努力遵循他们的建议,多年来我体验了用多种语言编程的经历。从我的多语言编程经历中,我学到的最重要的一课是:学习一门语言不仅仅是学习语法,你还需要理解它的文化。你可以用任何语言写 Fortran 风格的代码,但要真正掌握一门语言,你必须拥抱它。如果你的 C# 代码是一个长长的 `Main` 方法,里面大多是静态辅助方法,不要找借口,而是去学习为什么类是有意义的。如果你难以理解函数式语言中的 lambda 表达式,不要回避,强迫自己去使用它们。 + +一旦你掌握了新语言的技巧,你会惊讶地发现自己开始以新的方式使用已经熟悉的语言。通过编程 Ruby,我学会了如何在 C# 中有效地使用委托;释放 .NET 泛型的全部潜力让我想到如何让 Java 泛型更有用;LINQ 让我轻松自学了 Scala。 + +通过在不同语言之间切换,你还会对设计模式有更好的理解。C 程序员发现 C# 和 Java 已经将迭代器模式商品化了。在 Ruby 和其他动态语言中,你可能仍然会使用访问者模式,但你的实现不会像《设计模式》书中的示例那样。 + +有些人可能会认为《芬尼根的守灵夜》难以阅读,而另一些人则因其风格之美而称赞它。为了让这本书不那么令人生畏,已经有了单一语言的翻译版本。讽刺的是,第一个翻译版本是法语。代码在很多方面是相似的。如果你用一点 Python、一些 Java 和一点 Erlang 来写《芬尼根》式的代码,你的项目会变得一团糟。如果你探索新语言是为了扩展思维,获得如何以不同方式解决问题的新思路,你会发现,每学会一门新语言,你用熟悉的老语言编写的代码会变得更加优美。 + +作者:Anders Norås \ No newline at end of file diff --git a/zh/thing_28/README.md b/zh/thing_28/README.md new file mode 100644 index 00000000..a337adc9 --- /dev/null +++ b/zh/thing_28/README.md @@ -0,0 +1,19 @@ +# 不要将你的程序钉在直立的位置 + +我曾经写过一个恶搞的C++测验,其中我讽刺地提出了以下异常处理策略: + +> 通过在代码库中大量使用`try...catch`结构,我们有时能够防止应用程序中止。我们将这种结果状态称为“将尸体钉在直立的位置”。 + +尽管我是在开玩笑,但实际上我是在总结我从“痛苦经验女士”那里学到的一课。 + +这是我们自制的C++库中的一个基础应用程序类。多年来,它被许多程序员的手指戳过:没有人的手是干净的。它包含了处理所有其他代码逃逸异常的代码。受到《第二十二条军规》中尤索林的启发,我们决定,或者更确切地说是感觉(“决定”这个词暗示了比构建这个怪物时更多的思考),这个类的一个实例应该永远存在,或者在尝试中死去。 + +为此,我们交织了多个异常处理程序。我们将Windows的结构化异常处理与本机异常处理混合在一起(还记得C++中的`__try...__except`吗?我也不记得了)。当事情意外抛出异常时,我们尝试再次调用它们,更用力地按下参数。回想起来,我喜欢认为当我在另一个`catch`子句中编写内部的`try...catch`处理程序时,某种意识悄然袭来,我可能无意中从良好实践的高速公路上拐入了疯狂但诱人的小巷。然而,这可能是事后的智慧。 + +不用说,每当基于这个类的应用程序出现问题时,它们就像码头边的黑手党受害者一样消失,没有留下任何有用的气泡痕迹来指示到底发生了什么,尽管据说调用了转储例程来记录灾难。最终——经过漫长的等待——我们评估了我们所做的事情,并感到了羞耻。我们用最小且健壮的报告机制替换了整个混乱。但这已经是在许多崩溃之后了。 + +我不会用这个来打扰你——因为肯定没有人会像我们一样愚蠢——但因为我最近与一个学术头衔表明他应该更懂的人进行了一场在线争论。我们正在讨论远程事务中的Java代码。他认为,如果代码失败,它应该就地捕获并阻止异常。(“然后用它做什么?”我问。“做晚饭吗?”) + +他引用了UI设计者的规则:永远不要让用户看到异常报告,仿佛这就能解决问题,毕竟它是大写的。我想知道他是否负责那些蓝屏ATM机的代码,这些ATM机的照片装饰了那些较弱的博客,并且他因此受到了永久的精神创伤。无论如何,如果你遇到他,点头微笑,不要理会,然后悄悄走向门口。 + +作者:[Verity Stob](http://programmer.97things.oreilly.com/wiki/index.php/Verity_Stob) \ No newline at end of file diff --git a/zh/thing_29/README.md b/zh/thing_29/README.md new file mode 100644 index 00000000..d32eda1a --- /dev/null +++ b/zh/thing_29/README.md @@ -0,0 +1,21 @@ +# 不要依赖“魔法发生在这里” + +如果你从足够远的地方观察任何活动、过程或学科,它看起来都很简单。没有开发经验的管理者认为程序员的工作很简单,而没有管理经验的程序员也认为管理者的工作很简单。 + +编程是某些人有时会做的事情。而最困难的部分——思考——对于外行来说是最不可见和最不被重视的。几十年来,人们多次尝试消除对这种熟练思考的需求。最早且最令人难忘的尝试之一是Grace Hopper为使编程语言不那么晦涩而做出的努力——一些说法预测这将消除对专业程序员的需求。结果(COBOL)在随后的几十年里为许多专业程序员带来了收入。 + +对于理解编程复杂性的程序员来说,认为通过消除编程可以简化软件开发的持续愿景显然是天真的。但导致这种错误的心理过程是人类天性的一部分,程序员和其他人一样容易犯这种错误。 + +在任何项目中,可能有许多事情是单个程序员没有积极参与的:从用户那里获取需求、获得预算批准、设置构建服务器、将应用程序部署到QA和生产环境、将业务从旧流程或程序中迁移等。 + +当你不积极参与某些事情时,会有一种无意识的倾向,认为这些事情很简单,是“通过魔法”发生的。只要魔法继续发生,一切都很顺利。但当——通常是“当”而不是“如果”——魔法停止时,项目就会陷入困境。 + +我知道有些项目因为没有人理解他们依赖的是“正确”版本的DLL被加载而损失了数周的开发时间。当事情开始间歇性失败时,团队成员在其他地方寻找问题,直到有人注意到加载的是“错误”版本的DLL。 + +另一个部门运行得很顺利——项目按时交付,没有深夜调试会议,没有紧急修复。事实上,如此顺利,以至于高级管理层认为事情“自己运行”,他们可以不需要项目经理。不到六个月,该部门的项目就和其他部门一样——延迟、充满漏洞,并且不断修补。 + +你不必理解所有让你的项目运行的魔法,但了解其中的一些——或者欣赏那些理解你不懂的部分的人——并没有什么坏处。 + +最重要的是,确保当魔法停止时,它可以重新启动。 + +作者:[AlanGriffiths](http://programmer.97things.oreilly.com/wiki/index.php/AlanGriffiths) \ No newline at end of file diff --git a/zh/thing_30/README.md b/zh/thing_30/README.md new file mode 100644 index 00000000..1b706ab4 --- /dev/null +++ b/zh/thing_30/README.md @@ -0,0 +1,27 @@ +# 不要重复自己 + +在所有的编程原则中,“不要重复自己”(Don't Repeat Yourself,简称DRY)可能是最基本的原则之一。这一原则由Andy Hunt和Dave Thomas在《程序员修炼之道》中提出,并成为许多其他著名的软件开发最佳实践和设计模式的基础。学会识别重复代码,并理解如何通过适当的实践和正确的抽象来消除重复的开发人员,能够比那些不断在应用程序中引入不必要重复的开发人员编写出更干净的代码。 + +重复是浪费 +--- + +应用程序中的每一行代码都需要维护,并且是未来潜在的错误来源。重复不必要地膨胀了代码库,导致更多的错误机会,并为系统增加了意外的复杂性。重复给系统带来的膨胀还使得开发人员更难完全理解整个系统,或者确保在一个地方所做的更改不需要在重复逻辑的其他地方也进行更改。DRY原则要求“系统中的每一部分知识都必须有一个单一、明确、权威的表示”。 + +过程中的重复需要自动化 +--- + +软件开发中的许多过程是重复的,并且很容易自动化。DRY原则不仅适用于应用程序的源代码,也适用于这些上下文。手动测试速度慢、容易出错且难以重复,因此应尽可能使用自动化测试套件。如果手动集成软件,可能会耗费时间且容易出错,因此应尽可能频繁地运行构建过程,理想情况下每次提交代码时都应运行。任何可以自动化的繁琐手动过程都应被自动化和标准化。目标是确保只有一种完成任务的方式,并且这种方式尽可能无痛。 + +逻辑中的重复需要抽象 +--- + +逻辑中的重复可以采取多种形式。复制粘贴的*if-then*或*switch-case*逻辑是最容易检测和纠正的。许多设计模式的明确目标是减少或消除应用程序中的逻辑重复。如果一个对象通常需要完成几件事才能使用,可以通过抽象工厂或工厂方法来实现。如果一个对象的行为有许多可能的变化,可以使用策略模式来注入这些行为,而不是使用大型的*if-then*结构。事实上,设计模式本身的制定就是为了减少解决常见问题所需的重复工作,并讨论这些解决方案。此外,DRY原则也可以应用于结构,如数据库模式,从而实现规范化。 + +原则问题 +--- + +其他软件原则也与DRY相关。仅适用于代码功能行为的“一次且仅一次”原则可以被视为DRY的一个子集。开闭原则(Open/Closed Principle)指出“软件实体应该对扩展开放,对修改关闭”,只有在遵循DRY原则时,这一原则才能在实践中发挥作用。同样,著名的单一职责原则(Single Responsibility Principle)要求一个类“只有一个改变的理由”,这也依赖于DRY原则。 + +当在结构、逻辑、过程和功能方面遵循DRY原则时,它为软件开发人员提供了基本的指导,并有助于创建更简单、更易维护、更高质量的应用程序。虽然在某些情况下,为了满足性能或其他要求(例如数据库中的数据反规范化),重复可能是必要的,但它应该只在直接解决实际问题而不是想象问题时使用。 + +作者:[Steve Smith](http://programmer.97things.oreilly.com/wiki/index.php/Steve_Smith) \ No newline at end of file diff --git a/zh/thing_31/README.md b/zh/thing_31/README.md new file mode 100644 index 00000000..128a0ebc --- /dev/null +++ b/zh/thing_31/README.md @@ -0,0 +1,22 @@ +# 别碰那代码! + +我们每个人都曾经历过这样的时刻。你的代码被部署到预发布服务器上进行系统测试,测试经理反馈说她遇到了问题。你的第一反应是:“快,让我来修复它——我知道问题出在哪里。” + +但从更广泛的角度来看,真正的问题在于,作为开发者,你认为自己应该有权访问预发布服务器。 + +在大多数基于Web的开发环境中,架构可以这样分解: + +- 开发者的本地机器上进行本地开发和单元测试 +- 开发服务器上进行手动或自动化的集成测试 +- 预发布服务器上,QA团队和用户进行验收测试 +- 生产服务器 + +当然,中间还穿插着其他服务器和服务,比如源代码控制和问题跟踪系统,但你明白我的意思。在这种模式下,开发者——即使是高级开发者——也不应该拥有超越开发服务器的访问权限。大多数开发工作都是在开发者的本地机器上完成的,使用他们最喜欢的IDE、虚拟机,以及适量的“黑魔法”来祈求好运。 + +一旦代码被提交到源代码控制系统(SCC),无论是自动还是手动,它都应该被推送到开发服务器上,进行必要的测试和调整,以确保所有部分都能协同工作。但从这一刻起,开发者就变成了这个过程的旁观者。 + +预发布经理应该将代码打包并推送到预发布服务器上,供QA团队测试。就像开发者不需要访问开发服务器以外的任何东西一样,QA团队和用户也不需要触碰开发服务器上的任何东西。如果代码已经准备好进行验收测试,那就发布一个版本并推送,而不是让用户“快速看一下”开发服务器上的内容。记住,除非你是一个人独自开发项目,否则其他开发者也在那里有代码,他们可能还没准备好让用户看到。发布经理是唯一一个应该同时拥有访问权限的人。 + +在任何情况下——绝对不要——开发者都不应该拥有生产服务器的访问权限。如果出现问题,你的支持团队应该要么自己修复,要么请求你修复。在代码被提交到SCC后,他们会从那里推送一个补丁。我参与过的一些最大的编程灾难,就是因为有人(咳咳,就是我)违反了这条规则。如果出了问题,生产环境绝不是修复它的地方。 + +作者:[Cal Evans](http://programmer.97things.oreilly.com/wiki/index.php/Cal_Evans) \ No newline at end of file diff --git a/zh/thing_32/README.md b/zh/thing_32/README.md new file mode 100644 index 00000000..a9208a8b --- /dev/null +++ b/zh/thing_32/README.md @@ -0,0 +1,15 @@ +# 封装行为,而不仅仅是状态 + +在系统理论中,**封装**是处理大型复杂系统结构时最有用的构造之一。在软件行业中,封装的价值已被广泛理解。编程语言通过子程序、函数、模块、包、类等构造来支持封装。 + +模块和包解决了更大规模的封装需求,而类、子程序和函数则解决了更细粒度的封装问题。多年来,我发现类是开发者最难正确使用的封装构造之一。我们经常看到一个类中只有一个3000行的主方法,或者一个类中只有针对其基本属性的*set*和*get*方法。这些例子表明,相关开发者并未完全理解面向对象的思想,未能充分利用对象作为建模构造的能力。对于熟悉POJO(Plain Old Java Object)和POCO(Plain Old C# Object 或 Plain Old CLR Object)术语的开发者来说,回归面向对象作为建模范式的基础的初衷是——对象是简单而朴素的,但并不愚蠢。 + +一个对象封装了状态和行为,其中行为由实际状态定义。以一个门对象为例,它有四种状态:关闭、打开、正在关闭、正在打开。它提供了两个操作:打开和关闭。根据状态的不同,打开和关闭操作的行为也会有所不同。对象的这种固有特性使得设计过程在概念上变得简单。它归结为两个简单的任务:分配和委派责任给不同的对象,包括对象之间的交互协议。 + +通过一个例子可以最好地说明这一点。假设我们有三个类:`Customer`、`Order`和`Item`。`Customer`对象是信用额度和信用验证规则的自然载体。`Order`对象知道其关联的`Customer`,并且它的`addItem`操作通过调用`customer.validateCredit(item.price())`来委托实际的信用检查。如果该方法的后置条件失败,可以抛出异常并中止购买。 + +经验不足的面向对象开发者可能会决定将所有业务规则封装到一个通常称为`OrderManager`或`OrderService`的对象中。在这些设计中,`Order`、`Customer`和`Item`被视为几乎只是记录类型。所有逻辑都被从类中提取出来,并集中在一个大型的过程化方法中,其中包含大量的内部*if-then-else*结构。这些方法很容易被破坏,几乎无法维护。原因是什么?封装被破坏了。 + +因此,最终不要破坏封装,并利用编程语言的能力来维护它。 + +作者:[Einar Landre](http://programmer.97things.oreilly.com/wiki/index.php/Einar_Landre) \ No newline at end of file diff --git a/zh/thing_33/README.md b/zh/thing_33/README.md new file mode 100644 index 00000000..0f1d63a7 --- /dev/null +++ b/zh/thing_33/README.md @@ -0,0 +1,17 @@ +# 浮点数不是实数 + +浮点数在数学意义上并不是“实数”,尽管在某些编程语言(如Pascal和Fortran)中它们被称为*实数*。实数具有无限精度,因此是连续且无损的;而浮点数精度有限,所以它们是有限的,并且类似于“表现不佳”的整数,因为它们在范围内并不是均匀分布的。 + +举个例子,将2147483647(最大的32位有符号整数)赋值给一个32位浮点变量(比如x),然后打印它。你会看到2147483648。现在打印`x - 64`,结果仍然是2147483648。再打印`x - 65`,你会得到2147483520!为什么会这样?因为在该范围内相邻浮点数之间的间距是128,而浮点运算会四舍五入到最接近的浮点数。 + +IEEE浮点数是基于二进制科学计数法的固定精度数字:1.d1d2...dp-1 × 2e,其中*p*是精度(float为24,double为53)。两个连续数字之间的间距是21-p+e,可以安全地近似为ε|x|,其中ε是*机器epsilon*(21-p)。 + +了解浮点数附近的间距可以帮助你避免经典的数值错误。例如,如果你正在执行迭代计算,比如寻找方程的根,那么要求比数字系统在答案附近所能提供的更高精度是没有意义的。确保你请求的容差不小于该处的间距;否则你将永远循环下去。 + +由于浮点数是实数的近似值,不可避免地会存在一些误差。这种误差称为*舍入误差*,可能会导致令人惊讶的结果。例如,当你减去几乎相等的数字时,最高有效位会相互抵消,因此原本是最低有效位(舍入误差所在的位置)会被提升到浮点结果中的最高有效位,从而基本上污染了任何进一步的相关计算(这种现象称为*涂抹*)。你需要仔细检查你的算法,以防止这种*灾难性抵消*。举个例子,考虑用二次公式求解方程*x2 - 100000x + 1 = 0*。由于表达式*-b + sqrt(b2 - 4)*中的操作数在数量级上几乎相等,你可以改为计算根*r1 = -b + sqrt(b2 - 4)*,然后得到*r2 = 1/r1*,因为对于任何二次方程ax2 + bx + c = 0,根满足*r1r2 = c/a*。 + +涂抹可能以更微妙的方式发生。假设一个库天真地通过公式*1 + x + x2/2 + x3/3! + ...*计算*ex*。这对于正数*x*效果很好,但考虑一下当*x*是一个大的负数时会发生什么。偶数幂项会产生大的正数,而减去奇数幂的数量级甚至不会影响结果。这里的问题在于,大的正数项中的舍入误差位于比真实答案更重要的数字位置。答案会趋向于正无穷!这里的解决方案也很简单:对于负数*x*,计算*ex = 1/e|x|*。 + +不言而喻,你不应该将浮点数用于金融应用——这正是Python和C#等语言中的十进制类的用途。浮点数旨在用于高效的科学计算。但如果没有准确性,效率就毫无价值,所以请记住舍入误差的来源,并相应地编写代码! + +作者:[Chuck Allison](http://programmer.97things.oreilly.com/wiki/index.php/Chuck_Allison) \ No newline at end of file diff --git a/zh/thing_34/README.md b/zh/thing_34/README.md new file mode 100644 index 00000000..af2676a2 --- /dev/null +++ b/zh/thing_34/README.md @@ -0,0 +1,13 @@ +# 通过开源实现你的抱负 + +很有可能,你在工作中开发的软件并不能满足你对软件开发最雄心勃勃的幻想。也许你正在为一家大型保险公司开发软件,而你更愿意在谷歌、苹果、微软或你自己的初创公司工作,开发下一个大项目。如果你在为那些你不关心的系统开发软件,你将永远无法达到你想要的目标。 + +幸运的是,有一个解决你问题的答案:开源。有成千上万的开源项目,其中许多非常活跃,它们为你提供了任何你想要的软件开发经验。如果你喜欢开发操作系统的想法,就去帮助其中一个操作系统项目。如果你想从事音乐软件、动画软件、加密技术、机器人技术、PC游戏、大型在线玩家游戏、手机或任何其他领域的开发,你几乎肯定能找到至少一个致力于该兴趣的开源项目。 + +当然,天下没有免费的午餐。你必须愿意放弃你的空闲时间,因为你可能无法在白天的工作中开发一个开源视频游戏——你仍然对你的雇主负有责任。此外,很少有人通过为开源项目做贡献来赚钱——有些人确实能赚钱,但大多数人不能。你应该愿意放弃一些空闲时间(少玩电子游戏和看电视不会要了你的命)。你在开源项目上工作得越努力,你就能越快实现你作为程序员的真正抱负。考虑你的雇佣合同也很重要——一些雇主可能会限制你可以贡献的内容,即使是在你自己的时间里。此外,你还需要小心不要违反与版权、专利、商标和商业秘密相关的知识产权法律。 + +开源为有动力的程序员提供了巨大的机会。首先,你可以看到其他人如何实现你感兴趣的解决方案——通过阅读别人的源代码,你可以学到很多东西。其次,你可以将自己的代码和想法贡献给项目——并不是你所有的好想法都会被接受,但有些可能会,而且你只需通过解决解决方案和贡献代码就能学到新东西。第三,你会遇到和你一样对某种软件充满热情的优秀人才——这些开源友谊可以持续一生。第四,假设你是一个有能力的贡献者,你将能够在真正感兴趣的技术领域获得实际经验。 + +开始参与开源项目非常简单。有大量的文档介绍你需要的工具(例如,源代码管理、编辑器、编程语言、构建系统等)。首先找到你想参与的项目,并了解该项目使用的工具。大多数情况下,项目本身的文档会比较少,但这可能并不重要,因为学习的最好方法是自己研究代码。如果你想参与其中,你可以主动帮助编写文档。或者你可以从自愿编写测试代码开始。虽然这听起来可能并不令人兴奋,但事实是,通过为别人的软件编写测试代码,你几乎可以比任何其他软件活动学得更快。编写测试代码,真正好的测试代码。发现错误,建议修复,交朋友,开发你喜欢的软件,并实现你的软件开发抱负。 + +作者:[Richard Monson-Haefel](http://programmer.97things.oreilly.com/wiki/index.php/Richard_Monson-Haefel) \ No newline at end of file diff --git a/zh/thing_35/README.md b/zh/thing_35/README.md new file mode 100644 index 00000000..12ff7765 --- /dev/null +++ b/zh/thing_35/README.md @@ -0,0 +1,13 @@ +# API设计的黄金法则 + +API设计是困难的,尤其是在大规模设计中。如果你正在设计一个将拥有数百或数千用户的API,你必须考虑未来如何更改它,以及你的更改是否会破坏客户端代码。除此之外,你还必须考虑API用户对你的影响。如果你的API类在内部使用了它自己的某个方法,你必须记住用户可能会继承你的类并重写该方法,这可能是灾难性的。你将无法更改该方法,因为一些用户已经赋予了它不同的含义。你未来的内部实现选择将受制于你的用户。 + +API开发者以各种方式解决这个问题,但最简单的方法是锁定API。如果你使用Java,你可能会倾向于将大多数类和方法设为`final`。在C#中,你可能会将类和方法设为`sealed`。无论你使用哪种语言,你可能会倾向于通过单例模式或使用静态工厂方法来呈现你的API,以防止有人重写行为并以可能限制你未来选择的方式使用你的代码。这一切似乎都是合理的,但真的是这样吗? + +在过去十年中,我们逐渐意识到单元测试是实践中极其重要的一部分,但这一教训尚未完全渗透到整个行业。证据随处可见。随便拿一个使用第三方API的未经测试的类,尝试为其编写单元测试。大多数时候,你会遇到麻烦。你会发现使用API的代码像胶水一样粘在API上。没有办法模拟API类,以便你可以感知代码与它们的交互,或者为测试提供返回值。 + +随着时间的推移,这种情况会有所改善,但前提是我们在设计API时开始将测试视为一个真正的用例。不幸的是,这比仅仅测试我们的代码要复杂一些。这就是**API设计的黄金法则**的用武之地:*仅仅为你开发的API编写测试是不够的;你必须为使用你API的代码编写单元测试。当你这样做时,你会亲身体验到用户在他们尝试独立测试代码时必须克服的障碍。* + +没有一种方法可以让开发者轻松测试使用你API的代码。`static`、`final`和`sealed`并不是天生不好的结构。它们有时是有用的。但重要的是要意识到测试问题,要做到这一点,你必须亲身体验它。一旦你体验过,你就可以像处理其他设计挑战一样来处理它。 + +作者:[Michael Feathers](http://programmer.97things.oreilly.com/wiki/index.php/Michael_Feathers) \ No newline at end of file diff --git a/zh/thing_36/README.md b/zh/thing_36/README.md new file mode 100644 index 00000000..704dc0c7 --- /dev/null +++ b/zh/thing_36/README.md @@ -0,0 +1,17 @@ +# 大师神话 + +任何在软件行业工作过一段时间的人都听过类似这样的问题: + +> *我遇到了异常 XYZ。你知道问题出在哪里吗?* + +提问者很少会费心附上堆栈跟踪、错误日志或任何与问题相关的上下文。他们似乎认为你生活在另一个层面,认为解决方案会凭空出现在你脑海中,而不需要基于证据进行分析。他们认为你是大师。 + +我们通常期望那些不熟悉软件的人提出这样的问题:对他们来说,系统几乎像是魔法。让我担忧的是,这种现象在软件社区中也存在。类似的问题也出现在程序设计领域,比如“我正在构建库存管理系统。我应该使用乐观锁吗?”讽刺的是,提问者通常比被问者更有能力回答这个问题。提问者理应了解上下文和需求,并且可以阅读不同策略的优缺点。然而,他们却期望你在没有上下文的情况下给出一个明智的答案。他们期望魔法。 + +是时候让软件行业破除这种大师神话了。“大师”也是普通人。他们像我们一样运用逻辑并系统性地分析问题。他们依赖心理捷径和直觉。想想你见过的最优秀的程序员:在某个时刻,那个人对软件的了解比你现在的还要少。如果有人看起来像大师,那是因为他们多年来致力于学习和完善思维过程。所谓“大师”,不过是一个充满无尽好奇心的聪明人。 + +当然,天赋的差异仍然很大。许多黑客比我更聪明、知识更渊博、生产力更高。即便如此,破除大师神话也有积极的影响。例如,当与比我更聪明的人合作时,我会确保做好基础工作,提供足够的上下文,以便对方能够高效地运用他们的技能。破除大师神话也意味着消除了进步的心理障碍。我不再看到一个魔法般的屏障,而是一个我可以不断前进的连续体。 + +最后,软件行业最大的障碍之一是一些聪明人故意传播大师神话。这可能是出于自我,或者作为一种策略,以增加自己在客户或雇主眼中的价值。讽刺的是,这种态度可能会让聪明人变得不那么有价值,因为他们没有为同行的成长做出贡献。我们不需要大师。我们需要的是愿意培养该领域其他专家的专家。我们每个人都有成长的空间。 + +作者:[Ryan Brush](http://programmer.97things.oreilly.com/wiki/index.php/Ryan_Brush) \ No newline at end of file diff --git a/zh/thing_37/README.md b/zh/thing_37/README.md new file mode 100644 index 00000000..7e93b3e7 --- /dev/null +++ b/zh/thing_37/README.md @@ -0,0 +1,13 @@ +# 努力工作并不总是有回报 + +作为一名程序员,努力工作往往并不会带来相应的回报。你可能会欺骗自己和一些同事,让他们相信你通过在办公室长时间工作为项目做出了很多贡献。但事实是,通过减少工作时间,你可能会取得更多的成果——有时甚至是多得多的成果。如果你试图每周保持专注和“高效”超过30小时,那么你可能工作得太辛苦了。你应该考虑减少工作量,以提高效率并完成更多任务。 + +这种说法可能看起来违反直觉,甚至具有争议性,但这是编程和软件开发整体涉及持续学习过程的直接结果。当你在一个项目上工作时,你会更深入地理解问题领域,并希望找到更有效的方法来实现目标。为了避免浪费工作,你必须留出时间来观察你所做工作的效果,反思你所看到的事物,并相应地调整你的行为。 + +专业编程通常不像在铺好的道路上全力奔跑几公里,目标清晰可见。大多数软件项目更像是一场漫长的定向越野马拉松。在黑暗中。只有一张粗略的地图作为指引。如果你只是朝着一个方向出发,尽可能快地奔跑,你可能会给一些人留下深刻印象,但你不太可能成功。你需要保持可持续的速度,并在你了解更多关于你所在位置和前进方向时调整路线。 + +此外,你总是需要不断学习关于软件开发的一般知识,特别是编程技术。你可能需要阅读书籍、参加会议、与其他专业人士交流、尝试新的实现技术,并了解能够简化工作的强大工具。作为一名专业程序员,你必须保持自己在专业领域的知识更新——就像脑外科医生和飞行员被期望在自己的专业领域保持最新知识一样。你需要利用晚上、周末和假期来提升自己,因此你不能将这些时间用于当前项目的加班。你真的期望脑外科医生每周进行60小时的手术,或者飞行员每周飞行60小时吗?当然不是,准备和教育是他们职业的重要组成部分。 + +专注于项目,通过找到聪明的解决方案尽可能多地做出贡献,提高你的技能,反思你所做的事情,并调整你的行为。避免像笼子里的仓鼠一样在轮子上不停奔跑,让自己和我们的职业蒙羞。作为一名专业程序员,你应该知道,试图每周保持60小时的专注和“高效”并不是明智之举。像专业人士一样行动:准备、执行、观察、反思和改变。 + +作者:[Olve Maudal](http://programmer.97things.oreilly.com/wiki/index.php/Olve_Maudal) \ No newline at end of file diff --git a/zh/thing_38/README.md b/zh/thing_38/README.md new file mode 100644 index 00000000..31537832 --- /dev/null +++ b/zh/thing_38/README.md @@ -0,0 +1,23 @@ +# 如何使用缺陷跟踪系统 + +无论你称它们为*缺陷*、*故障*,还是*设计副作用*,你都无法避免它们。知道如何提交一个好的缺陷报告,以及如何阅读缺陷报告,是保持项目顺利推进的关键技能。 + +一个好的缺陷报告需要包含以下三点: + +- 如何重现缺陷,尽可能精确地描述,以及重现的频率。 +- 你认为应该发生什么。 +- 实际发生了什么,或者至少是你记录的信息。 + +缺陷报告中信息的数量和质量不仅反映了缺陷本身,也反映了报告者的水平。愤怒且简短的缺陷报告(如“这个功能太烂了!”)只会告诉开发者你当时心情不好,但除此之外没有提供任何有用的信息。一个包含大量上下文信息、易于重现的缺陷报告会赢得所有人的尊重,即使它可能会阻止一次发布。 + +缺陷报告就像一场对话,所有的历史记录都公开在大家面前。不要责怪他人或否认缺陷的存在。相反,请求更多信息或考虑你可能遗漏了什么。 + +改变缺陷的状态,例如从*开放*改为*关闭*,是公开表明你对缺陷的看法。花时间解释为什么你认为缺陷应该被关闭,可以避免日后向沮丧的管理者和客户解释时的繁琐工作。改变缺陷的优先级也是一种类似的公开声明,即使对你来说这个问题微不足道,也不意味着它不会阻止其他人使用产品。 + +不要为了自己的目的而滥用缺陷的字段。在缺陷的主题字段中添加“VITAL:”可能会让你更容易对某些报告的结果进行排序,但最终其他人也会效仿,不可避免地会出现拼写错误,或者需要在其他报告中删除这个标记。相反,使用一个新的值或字段,并记录该字段的使用方式,这样其他人就不必重复操作。 + +确保每个人都知道如何找到团队应该处理的缺陷。这通常可以通过一个名称明显的公共查询来实现。确保每个人都在使用相同的查询,并且在更新查询之前,先通知团队你正在改变大家的工作内容。 + +最后,记住缺陷并不是标准的工作单位,就像一行代码并不能精确衡量工作量一样。 + +作者:[Matt Doar](http://programmer.97things.oreilly.com/wiki/index.php/Matt_Doar) \ No newline at end of file diff --git a/zh/thing_39/README.md b/zh/thing_39/README.md new file mode 100644 index 00000000..eb316567 --- /dev/null +++ b/zh/thing_39/README.md @@ -0,0 +1,24 @@ +# 通过删除代码来改进代码 + +*少*即是多。这是一句相当陈旧的格言,但有时它确实是对的。 + +在过去的几周里,我对我们的代码库做了一些改进,其中一项就是删除了其中的一部分代码。 + +我们遵循XP(极限编程)的原则编写了软件,包括YAGNI(即,你不需要它)。然而,人性使然,我们不可避免地会在某些地方做得不够好。 + +我注意到产品在执行某些任务时花费了太长时间——这些任务本应该是几乎瞬间完成的简单任务。这是因为它们被过度实现了;添加了许多不必要的额外功能,这些功能在当时看起来似乎是个好主意。 + +因此,我通过从代码库中删除这些多余的功能,简化了代码,提高了产品性能,并降低了全局代码的熵值。幸运的是,我的单元测试告诉我,在操作过程中我没有破坏其他任何东西。 + +这是一次简单而令人满意的体验。 + +那么,为什么这些不必要的代码会出现在代码库中呢?为什么一个程序员会觉得有必要编写额外的代码,而它又是如何通过审查或结对编程过程的呢?几乎可以肯定的是,原因如下: + +- 这是一些有趣的额外内容,程序员想写它。*(提示:编写代码是因为它增加了价值,而不是因为它让你感到有趣。)* +- 有人认为将来可能会需要它,所以觉得最好现在就编写它。*(提示:这不是YAGNI。如果你现在不需要它,就不要现在编写它。)* +- 它看起来并不是一个很大的“额外”功能,所以实现它比回去询问客户是否真的需要它更容易。*(提示:编写和维护额外的代码总是需要更长的时间。而且客户实际上是非常容易接近的。一小段额外的代码会随着时间的推移滚雪球般地变成一大块需要维护的工作。)* +- 程序员发明了一些既没有文档记录也没有讨论过的额外需求,这些需求为额外功能提供了理由。实际上,这些需求是虚假的。*(提示:程序员不设定系统需求;客户才是决定需求的人。)* + +你现在在做什么工作?这些工作都是必要的吗? + +作者:[Pete Goodliffe](http://programmer.97things.oreilly.com/wiki/index.php/Pete_Goodliffe) \ No newline at end of file diff --git a/zh/thing_40/README.md b/zh/thing_40/README.md new file mode 100644 index 00000000..e2ec41e1 --- /dev/null +++ b/zh/thing_40/README.md @@ -0,0 +1,24 @@ +# 安装我 + +我对你的程序一点也不感兴趣。 + +我周围都是问题,待办事项清单长得像我的手臂一样。我现在访问你的网站的唯一原因是因为我听到了一个不太可能的传言,说你的软件能解决我所有的问题。如果我持怀疑态度,请你原谅。 + +如果眼球追踪研究是正确的,我已经读了标题,并且正在寻找标有*立即下载*的蓝色下划线文本。顺便说一句,如果我通过英国IP的Linux浏览器访问此页面,我可能想要从欧洲镜像下载Linux版本,所以请不要问。假设文件对话框立即打开,我会将文件保存到我的下载文件夹并继续阅读。 + +我们都会不断地对我们所做的每件事进行成本效益分析。如果你的项目在某一瞬间低于我的阈值,我会放弃它并转向其他事情。即时满足是最好的。 + +第一个障碍是*安装*。你觉得这不是什么大问题吗?现在去你的下载文件夹看看。里面全是*tar*和*zip*文件吧?你有多少解压了?有多少安装了?如果你像我一样,只有三分之一的东西除了占用硬盘空间外没什么用。 + +我可能想要门到门的便利,但我不希望你未经邀请就进入我的房子。在输入安装命令之前,我想确切地知道你把东西放在哪里。这是我的电脑,我喜欢尽可能地保持整洁。我还希望能够在我对它失去兴趣的那一刻立即删除你的程序。如果我怀疑这不可能,我一开始就不会安装它。我的机器现在很稳定,我希望保持这种状态。 + +如果你的程序是基于GUI的,那么我想做一些简单的事情并看到结果。向导没有帮助,因为它们做的事情我不理解。很可能我想读取一个文件,或者写一个文件。我不想创建项目、导入目录或告诉你我的电子邮件地址。如果一切正常,就进入教程。 + +如果你的软件是一个库,那么我会继续阅读你的网页,寻找一个*快速入门指南*。我想要一个五行的“Hello world”示例,输出与你的网站描述完全一致。不需要填写大型XML文件或模板,只需要一个简单的脚本。记住,我也下载了你竞争对手的框架。你知道的,那个在论坛上总是声称比你的好得多的那个?如果一切正常,就进入教程。 + +有一个教程,对吧?一个用我能理解的语言与我交流的教程? + +如果教程提到了我的问题,我会感到高兴。现在我正在阅读我可以做的事情,这开始变得有趣,甚至好玩。我会靠在椅子上,啜饮我的茶——我提到过我是来自英国的吗?——我会玩你的示例并学习使用你的创作。如果它解决了我的问题,我会给你发一封感谢邮件。当它崩溃时,我会发送错误报告,也会提出功能建议。我甚至会告诉所有朋友你的软件是最好的,尽管我从未尝试过你竞争对手的软件。而这一切都是因为你在我最初的试探性步骤上如此用心。 +我怎么会曾经怀疑你呢? + +作者:[Marcus Baker](http://programmer.97things.oreilly.com/wiki/index.php/Marcus_Baker) \ No newline at end of file diff --git a/zh/thing_41/README.md b/zh/thing_41/README.md new file mode 100644 index 00000000..c41525db --- /dev/null +++ b/zh/thing_41/README.md @@ -0,0 +1,13 @@ +# 进程间通信影响应用程序响应时间 + +响应时间对软件可用性至关重要。没有什么比等待某个软件系统响应更令人沮丧的了,尤其是当我们与软件的交互涉及反复的刺激和响应循环时。我们感觉软件在浪费我们的时间,影响我们的工作效率。然而,导致响应时间不佳的原因却鲜为人知,尤其是在现代应用程序中。许多性能管理文献仍然关注数据结构和算法,这些问题在某些情况下可能会产生影响,但在现代多层企业应用程序中,它们不太可能主导性能。 + +当这类应用程序出现性能问题时,我的经验是,检查数据结构和算法并不是寻找改进的正确方向。响应时间主要取决于响应刺激时进行的远程进程间通信(IPC)的数量。虽然可能存在其他本地瓶颈,但远程进程间通信的数量通常占主导地位。每次远程进程间通信都会为整体响应时间增加一些不可忽视的延迟,这些单独的延迟会累积起来,尤其是在它们按顺序发生时。 + +一个典型的例子是使用对象-关系映射的应用程序中的*涟漪加载*。涟漪加载描述了为构建对象图而执行的许多数据库调用的顺序执行(参见Martin Fowler的《企业应用架构模式》中的[Lazy Load](http://martinfowler.com/eaaCatalog/lazyLoad.html))。当数据库客户端是呈现网页的中间层应用服务器时,这些数据库调用通常在一个线程中顺序执行。它们的单独延迟会累积,从而影响整体响应时间。即使每个数据库调用只需要10毫秒,一个需要1000次调用的页面(这并不罕见)也会表现出至少10秒的响应时间。其他例子包括Web服务调用、来自Web浏览器的HTTP请求、分布式对象调用、请求-回复消息传递以及通过自定义网络协议进行的数据网格交互。响应刺激所需的远程IPC越多,响应时间就越长。 + +有一些相对明显且众所周知的策略可以减少每次刺激的远程进程间通信数量。一种策略是应用简约原则,优化进程之间的接口,以便以最少的交互量交换当前所需的精确数据。另一种策略是尽可能并行化进程间通信,从而使整体响应时间主要由延迟最长的IPC决定。第三种策略是缓存先前IPC的结果,以便通过命中本地缓存来避免未来的IPC。 + +在设计应用程序时,请注意响应每个刺激的进程间通信数量。在分析性能不佳的应用程序时,我经常发现IPC与刺激的比例高达数千比一。通过缓存、并行化或其他技术来降低这一比例,将比更改数据结构选择或调整排序算法带来更大的回报。 + +作者:[Randy Stafford](http://programmer.97things.oreilly.com/wiki/index.php/Randy_Stafford) \ No newline at end of file diff --git a/zh/thing_42/README.md b/zh/thing_42/README.md new file mode 100644 index 00000000..607faa84 --- /dev/null +++ b/zh/thing_42/README.md @@ -0,0 +1,15 @@ +# 保持构建的清洁 + +你是否曾经看过一份长得像一篇糟糕代码论文的编译器警告列表,然后心里想着:“你知道,我真的应该做点什么……但我现在没时间?”另一方面,你是否曾经看到编译过程中突然出现的一个孤零零的警告,然后立即修复了它? + +当我从头开始一个新项目时,没有警告,没有杂乱,没有问题。但随着代码库的增长,如果我不注意,杂乱、冗余、警告和问题就会开始堆积。当有很多噪音时,在数百个我不关心的警告中找到我真正想读的警告就变得困难得多。 + +为了让警告再次有用,我尝试对构建中的警告采取零容忍政策。即使警告不重要,我也会处理它。如果不重要但仍然相关,我会修复它。如果编译器警告潜在的空指针异常,我会修复原因——即使我“知道”这个问题永远不会在生产环境中出现。如果嵌入式文档(如Javadoc或类似文档)引用了已被删除或重命名的参数,我会清理文档。 + +如果我真的不关心并且真的不重要,我会询问团队是否可以更改我们的警告策略。例如,我发现记录方法的参数和返回值在许多情况下并没有增加任何价值,因此如果它们缺失,不应该发出警告。或者,升级到新版本的编程语言可能会使以前没问题的代码现在发出警告。例如,当Java 5引入泛型时,所有未指定泛型类型参数的旧代码都会发出警告。这是一种我不想被烦扰的警告(至少现在不想)。拥有一组与现实脱节的警告对任何人都没有好处。 + +通过确保构建始终是干净的,我就不必每次遇到警告时都决定它是否无关紧要。忽略事情是脑力劳动,我需要尽可能摆脱所有不必要的脑力劳动。保持构建的清洁也使其他人更容易接手我的工作。如果我留下警告,其他人将不得不费力区分哪些是相关的,哪些不是。或者更有可能的是,直接忽略所有警告,包括那些重要的警告。 + +构建中的警告是有用的。你只需要摆脱噪音,开始注意到它们。不要等待大扫除。当出现你不希望看到的东西时,立即处理它。要么修复警告的源头,抑制这个警告,要么修复你工具的警告策略。保持构建的清洁不仅仅是保持没有编译错误或测试失败:警告也是代码卫生的一个重要且关键的部分。 + +作者:[Johannes Brodwall](http://programmer.97things.oreilly.com/wiki/index.php/Johannes_Brodwall) \ No newline at end of file diff --git a/zh/thing_43/README.md b/zh/thing_43/README.md new file mode 100644 index 00000000..ecdce907 --- /dev/null +++ b/zh/thing_43/README.md @@ -0,0 +1,13 @@ +# 学会使用命令行工具 + +如今,许多软件开发工具都以集成开发环境(IDE)的形式打包。微软的Visual Studio和开源的Eclipse是两个流行的例子,尽管还有很多其他的IDE。IDE有很多值得喜欢的地方。它们不仅易于使用,还让程序员无需考虑构建过程中的许多小细节。 + +然而,易用性也有其缺点。通常,当一个工具易于使用时,这是因为工具在背后为你做出了许多决定,并自动完成了许多事情。因此,如果你只使用IDE作为编程环境,你可能永远不会完全理解你的工具实际上在做什么。你点击一个按钮,一些魔法发生了,然后一个可执行文件出现在项目文件夹中。 + +通过使用命令行构建工具,你将更深入地了解在构建项目时工具在做什么。编写自己的make文件将帮助你理解构建可执行文件的所有步骤(编译、汇编、链接等)。尝试这些工具的许多命令行选项也是一种宝贵的学习经验。要开始使用命令行构建工具,你可以使用开源的命令行工具,如GCC,或者使用你专有IDE附带的工具。毕竟,一个设计良好的IDE只是一组命令行工具的图形前端。 + +除了提高你对构建过程的理解外,有些任务使用命令行工具比使用IDE更容易或更高效。例如,*grep*和*sed*工具提供的搜索和替换功能通常比IDE中的功能更强大。命令行工具天生支持脚本编写,这使得自动化任务成为可能,例如生成每日定时构建、创建项目的多个版本以及运行测试套件。在IDE中,这种自动化可能更难(如果不是不可能的话),因为构建选项通常通过GUI对话框指定,构建过程通过鼠标点击启动。如果你从未走出IDE,你可能甚至不会意识到这些自动化任务是可能的。 + +但是等等。IDE的存在不就是为了让开发更容易,提高程序员的生产力吗?是的。这里提出的建议并不是你应该停止使用IDE。建议是你应该“揭开引擎盖”,了解你的IDE在为你做什么。最好的方法是学会使用命令行工具。然后,当你回到使用IDE时,你会更清楚地理解它在为你做什么,以及如何控制构建过程。另一方面,一旦你掌握了命令行工具的使用,并体验到它们提供的强大功能和灵活性,你可能会发现自己更喜欢命令行而不是IDE。 + +作者:[Carroll Robinson](http://programmer.97things.oreilly.com/wiki/index.php/Carroll_Robinson) \ No newline at end of file diff --git a/zh/thing_44/README.md b/zh/thing_44/README.md new file mode 100644 index 00000000..f9edff36 --- /dev/null +++ b/zh/thing_44/README.md @@ -0,0 +1,19 @@ +# 精通两种以上的编程语言 + +长期以来,编程心理学研究表明,编程专家的水平与程序员熟悉的不同编程范式的数量直接相关。这里所说的“熟悉”不仅仅是了解或略知一二,而是真正能够用这些范式进行编程。 + +每个程序员都是从一种编程语言开始的。这种语言对程序员思考软件的方式有着主导性的影响。无论程序员使用这种语言积累了多少年的经验,如果他们一直停留在这种语言上,他们就只会了解这一种语言。**单一语言**的程序员的思维方式会受到这种语言的限制。 + +学习第二种语言的程序员将会面临挑战,尤其是当这种语言的计算模型与第一种语言不同时。C、Pascal、Fortran 都有着相同的基本计算模型。从 Fortran 转向 C 会带来一些挑战,但并不多。从 C 或 Fortran 转向 C++ 或 Ada 则会在程序行为方式上带来根本性的挑战。从 C++ 转向 Haskell 是一个巨大的变化,因此也是一个巨大的挑战。从 C 转向 Prolog 则是一个非常明确的挑战。 + +我们可以列举出几种计算范式:过程式、面向对象、函数式、逻辑式、数据流式等。在这些范式之间切换会带来最大的挑战。 + +为什么这些挑战是有益的?这与我们思考算法实现的方式以及适用的实现模式和习惯用法有关。特别是,**跨范式融合**是专家技能的核心。适用于一种语言的问题解决习惯用法在另一种语言中可能无法实现。尝试将一种语言的习惯用法移植到另一种语言中,可以让我们更好地理解这两种语言以及正在解决的问题。 + +编程语言的跨范式融合有着巨大的影响。最明显的可能是,在命令式语言实现的系统中,声明式表达方式的使用越来越多。任何熟悉函数式编程的人都可以轻松地在使用 C 这样的语言时应用声明式方法。使用声明式方法通常会使程序更短且更易于理解。例如,C++ 通过全力支持泛型编程,几乎必然需要声明式的表达方式。 + +这一切的后果是,每个程序员都应该熟练掌握至少两种不同的编程范式,理想情况下至少是上述五种。程序员应该始终对学习新语言感兴趣,尤其是来自不熟悉的范式的语言。即使日常工作总是使用同一种编程语言,当一个人能够从其他范式中进行跨范式融合时,对该语言的使用复杂度的提升也不应被低估。雇主应该意识到这一点,并在培训预算中允许员工学习当前未使用的语言,以此提高对现有语言的使用复杂度。 + +虽然这是一个开始,但一周的培训课程不足以学习一门新语言:通常需要几个月的时间,即使是兼职使用,才能获得一门语言的真正工作知识。重要的是使用的习惯用法,而不仅仅是语法和计算模型。 + +作者:[Russel Winder](http://programmer.97things.oreilly.com/wiki/index.php/Russel_Winder) \ No newline at end of file diff --git a/zh/thing_45/README.md b/zh/thing_45/README.md new file mode 100644 index 00000000..d0a0ae92 --- /dev/null +++ b/zh/thing_45/README.md @@ -0,0 +1,23 @@ +# 了解你的IDE + +在20世纪80年代,我们的编程环境通常不过是美化过的文本编辑器……如果我们幸运的话。如今我们视为理所当然的语法高亮在当时是一种奢侈品,并不是每个人都能享受到的。用来美化代码格式的漂亮打印工具通常是需要运行的外部工具,以纠正我们的缩进。调试器也是单独的程序,用于逐步执行代码,但需要记住许多晦涩的快捷键。 + +到了20世纪90年代,公司开始意识到为程序员提供更好、更有用的工具可以带来潜在的收入。集成开发环境(IDE)将之前的编辑功能与编译器、调试器、漂亮打印工具和其他工具结合在一起。在那个时期,菜单和鼠标也变得流行起来,这意味着开发者不再需要学习晦涩的快捷键组合来使用他们的编辑器。他们可以直接从菜单中选择命令。 + +进入21世纪,IDE已经变得如此普遍,以至于一些公司为了在其他领域获得市场份额而免费提供它们。现代IDE配备了令人惊叹的功能。我最喜欢的是自动重构,特别是**提取方法**功能,我可以选择一段代码并将其转换为一个方法。重构工具会自动识别所有需要传递给方法的参数,这使得修改代码变得极其容易。我的IDE甚至会检测到其他可以被该方法替换的代码块,并询问我是否也想替换它们。 + +现代IDE的另一个令人惊叹的功能是能够在公司内部强制执行代码风格规则。例如,在Java中,一些程序员开始将所有参数声明为`final`(在我看来,这是浪费时间)。然而,既然他们有这种风格规则,我只需要在IDE中设置一下:任何非`final`参数都会收到警告。风格规则还可以用来发现潜在的bug,比如比较自动装箱对象的引用相等性,例如在自动装箱为引用对象的原始值上使用`==`。 + +不幸的是,现代IDE并不需要我们投入太多精力去学习如何使用它们。当我第一次在Unix上编写C语言时,我不得不花相当多的时间学习`vi`编辑器的使用,因为它的学习曲线非常陡峭。这些前期投入的时间在多年后得到了丰厚的回报。我甚至用`vi`来撰写这篇文章的草稿。现代IDE的学习曲线非常平缓,这可能导致我们永远停留在工具的最基本使用上。 + +我学习IDE的第一步是记住键盘快捷键。由于我在编写代码时手指放在键盘上,按下`Ctrl+Shift+I`来内联变量可以保持流畅,而切换到用鼠标导航菜单则会打断这种流畅。这些中断会导致不必要的上下文切换,如果我试图以懒惰的方式完成所有事情,我的效率会大大降低。同样的规则也适用于键盘技能:学会盲打,你不会后悔前期投入的时间。 + +最后,作为程序员,我们有一些经过时间考验的Unix流处理工具,可以帮助我们操作代码。例如,在代码审查过程中,如果我注意到程序员命名了许多相同的类,我可以使用`find`、`sed`、`sort`、`uniq`和`grep`等工具轻松找到这些类,如下所示: + +``` +find . -name "*.java" | sed 's/.*\///' | sort | uniq -c | grep -v "^ *1 " | sort -r +``` + +我们期望来家里的水管工能够使用他的喷灯。让我们花点时间研究如何更有效地使用我们的IDE。 + +作者:[Heinz Kabutz](http://programmer.97things.oreilly.com/wiki/index.php/Heinz_Kabutz) \ No newline at end of file diff --git a/zh/thing_46/README.md b/zh/thing_46/README.md new file mode 100644 index 00000000..58299848 --- /dev/null +++ b/zh/thing_46/README.md @@ -0,0 +1,47 @@ +# 了解你的极限 + +> *“人必须知道自己的极限。” — 肮脏的哈里* + +你的资源是有限的。你只有有限的时间和金钱来完成你的工作,包括保持你的知识、技能和工具最新所需的时间和金钱。你只能工作得如此努力、如此快速、如此聪明、如此长久。你的工具只有如此强大。你的目标机器只有如此强大。因此,你必须尊重你的资源极限。 + +如何尊重这些极限?了解你自己,了解你的团队,了解你的预算,了解你的工具。特别是,作为一名软件工程师,了解你的数据结构和算法的空间和时间复杂度,以及系统的架构和性能特征。你的工作是创建软件和系统的最佳结合。 + +空间和时间复杂度以函数 *O(f(n))* 表示,其中 n 等于输入的大小,表示随着 n 增长到无穷大时所需的渐近空间或时间。*f(n)* 的重要复杂度类别包括 *ln(n)*、*n*、*n ln(n)*、*ne* 和 *en*。正如绘制这些函数所清楚显示的,随着 *n* 变大,*O(ln(n))* 远远小于 *O(n)* 和 *O(n ln(n))*,而后者又远远小于 *O(ne)* 和 *O(en)*。正如 Sean Parent 所说,对于可实现的 n,所有复杂度类别都接近于常数、线性或无限。 + +![](http://programmer.97things.oreilly.com/wiki/images/c/c0/Clearly.jpeg) + +| | 访问时间 | 容量 | +|--------------|-----------------:| ----------:| +| 寄存器 | < 1 ns | 64b | +| 缓存行 | | 64B | +| L1 缓存 | 1 ns | 64 KB | +| L2 缓存 | 4 ns | 8 MB | +| 内存 | 20 ns | 32 GB | +| 磁盘 | 10 ms | 10 TB | +| 局域网 | 20 ms | > 1 PB | +| 互联网 | 100 ms | > 1 ZB | + +复杂度分析是基于抽象机器的,但软件运行在真实机器上。现代计算机系统组织为物理和虚拟机器的层次结构,包括语言运行时、操作系统、CPU、缓存、随机存取内存、磁盘驱动器和网络。第一个表格展示了一个典型网络服务器的随机访问时间和存储容量的限制。 + +注意,容量和速度相差几个数量级。缓存和预取在我们的系统的每一层都被大量使用,以隐藏这种差异,但它们只有在访问可预测时才有效。当缓存未命中频繁时,系统将会颠簸。例如,随机检查硬盘上的每个字节可能需要 32 年。即使随机检查内存中的每个字节也可能需要 11 分钟。随机访问是不可预测的。什么是可预测的?这取决于系统,但重新访问最近使用的项目和顺序访问项目通常是有效的。 + +算法和数据结构在使用缓存方面的效率各不相同。例如: +- 线性搜索充分利用了预取,但需要 *O(n)* 次比较。 +- 对排序数组的二分搜索只需要 *O(log(n))* 次比较。 +- van Emde Boas 树的搜索是 *O(log(n))* 并且是缓存无关的。 + +|元素数量 | 搜索时间 (ns)| | | +|:--------|-----------:|-----------:|--------:| +| | **线性** | **二分** | **vEB** | +| 8 | 50 | 90 | 40 | +| 64 | 180 | 150 | 70 | +| 512 | 1200 | 230 | 100 | +| 4096 | 17000 | 320 | 160 | + +如何选择?最终,通过测量。第二个表格展示了通过这三种方法搜索 64 位整数数组所需的时间。在我的电脑上: +- 线性搜索在小数组上具有竞争力,但在大数组上呈指数级下降。 +- van Emde Boas 由于其可预测的访问模式而轻松胜出。 + +> *“你付钱,你选择。” — [Punch](http://www.nytimes.com/1988/02/28/magazine/on-language-you-pays-yer-money.html?pagewanted=all)* + +作者:[Greg Colvin](http://programmer.97things.oreilly.com/wiki/index.php/Greg_Colvin) \ No newline at end of file diff --git a/zh/thing_47/README.md b/zh/thing_47/README.md new file mode 100644 index 00000000..823f5c14 --- /dev/null +++ b/zh/thing_47/README.md @@ -0,0 +1,19 @@ +# 了解你的下一次提交 + +我拍了拍三位程序员的肩膀,问他们在做什么。第一个回答:“我正在重构这些方法。”第二个回答:“我正在给这个Web操作添加一些参数。”第三个回答:“我正在处理这个用户故事。” + +乍一看,前两位似乎沉浸在他们工作的细节中,而只有第三位能看到大局,似乎后者更有全局观。然而,当我问他们何时以及会提交什么时,情况发生了戏剧性的变化。前两位非常清楚会涉及哪些文件,并且会在大约一小时内完成。第三位程序员回答:“哦,我想我几天内就能准备好。我可能会添加几个类,并可能以某种方式修改那些服务。” + +前两位并不缺乏对整体目标的愿景。他们选择了他们认为能朝着有成效方向发展的任务,并且可以在几个小时内完成。一旦他们完成了这些任务,他们会选择一个新的功能或重构来继续工作。因此,所有编写的代码都有一个明确的目的和一个有限的、可实现的目标。 + +第三位程序员未能将问题分解,而是同时处理所有方面。他根本不知道需要做什么,基本上是在进行推测性编程,希望最终能够提交。很可能在这个长时间会话开始时编写的代码与最终得出的解决方案不太匹配。 + +如果前两位程序员的任务超过两个小时,他们会怎么做?在意识到自己承担了太多任务后,他们很可能会丢弃他们的更改,定义更小的任务,然后重新开始。继续工作会缺乏重点,并导致推测性代码进入代码库。相反,更改会被丢弃,但获得的见解会被保留。 + +第三位程序员可能会继续猜测,并拼命尝试将他的更改拼凑成可以提交的东西。毕竟,你不能丢弃你已经完成的代码更改——那将是浪费工作,不是吗?不幸的是,不丢弃代码会导致一些稍微奇怪的、缺乏明确目的的代码进入代码库。 + +在某些时候,即使是专注于提交的程序员也可能无法找到他们认为可以在两小时内完成的有用内容。然后,他们会直接进入推测模式,摆弄代码,当然,每当一些见解使他们回到正轨时,就会丢弃更改。即使是这些看似无结构的黑客会话也有其目的:了解代码,以便能够定义一个构成有成效步骤的任务。 + +了解你的下一次提交。如果你无法完成,丢弃你的更改,然后根据你获得的见解定义一个你相信的新任务。在需要时进行推测性实验,但不要让自己不知不觉地陷入推测模式。不要将猜测性的工作提交到你的代码库中。 + +作者:[Dan Bergh Johnsson](http://programmer.97things.oreilly.com/wiki/index.php/Dan_Bergh_Johnsson) \ No newline at end of file diff --git a/zh/thing_48/README.md b/zh/thing_48/README.md new file mode 100644 index 00000000..ad880c92 --- /dev/null +++ b/zh/thing_48/README.md @@ -0,0 +1,15 @@ +# 大型互连数据属于数据库 + +如果你的应用程序需要处理大量、持久且互连的数据元素,不要犹豫,将其存储在关系数据库中。过去,关系数据库管理系统(RDBMS)曾经是昂贵、稀缺、复杂且难以驾驭的庞然大物。但如今情况已大不相同。现在的RDBMS系统很容易找到——很可能你正在使用的系统中已经安装了一两个。一些非常强大的RDBMS,如MySQL和PostgreSQL,都可以作为开源软件使用,因此购买成本不再是问题。更棒的是,所谓的嵌入式数据库系统可以作为库直接链接到你的应用程序中,几乎不需要设置或管理——两个著名的开源嵌入式数据库是SQLite和HSQLDB。这些系统非常高效。 + +如果你的应用程序的数据量超过了系统的内存容量,索引化的RDBMS表的性能将比库中的映射集合类型快几个数量级,后者会导致虚拟内存页面的频繁交换。现代数据库产品可以轻松地随着你的需求增长。通过适当的维护,你可以在需要时将嵌入式数据库扩展为更大的数据库系统。之后,你还可以从免费的开源产品切换到支持更好或功能更强大的专有系统。 + +一旦你掌握了SQL,编写以数据库为中心的应用程序将变得非常愉快。当你将经过适当规范化的数据存储在数据库中后,通过一个可读的SQL查询就可以轻松高效地提取信息;无需编写任何复杂的代码。同样,一条SQL命令就可以执行复杂的数据更改。对于一次性修改,比如改变持久数据的组织方式,你甚至不需要编写代码:只需启动数据库的直接SQL接口即可。这个接口还允许你进行查询实验,绕过常规编程语言的编译-编辑周期。 + +基于RDBMS编写代码的另一个优势在于处理数据元素之间的关系。你可以以声明的方式描述数据的一致性约束,避免在边缘情况下忘记更新数据时出现悬空指针的风险。例如,你可以指定如果删除了一个用户,那么该用户发送的消息也应该被删除。 + +你还可以随时在数据库中存储的实体之间创建高效的链接,只需创建一个索引即可。无需进行昂贵且广泛的类字段重构。此外,围绕数据库编写代码允许多个应用程序以安全的方式访问你的数据。这使得你可以轻松升级应用程序以支持并发使用,并且可以使用最合适的语言和平台编写应用程序的每个部分。例如,你可以用Java编写基于Web的应用程序的XML后端,用Ruby编写一些审计脚本,并用[Processing](http://www.processing.org/)编写可视化界面。 + +最后,请记住,RDBMS会竭尽全力优化你的SQL命令,让你可以专注于应用程序的功能,而不是算法调优。先进的数据库系统甚至会在背后利用多核处理器。随着技术的进步,你的应用程序性能也会随之提升。 + +作者:[Diomidis Spinellis](http://programmer.97things.oreilly.com/wiki/index.php/Diomidis_Spinellis) \ No newline at end of file diff --git a/zh/thing_49/README.md b/zh/thing_49/README.md new file mode 100644 index 00000000..eee322f2 --- /dev/null +++ b/zh/thing_49/README.md @@ -0,0 +1,19 @@ +# 学习外语 + +程序员需要大量的沟通。 + +在程序员的职业生涯中,有些阶段大部分的沟通似乎都是与计算机进行的。更准确地说,是与运行在计算机上的程序进行沟通。这种沟通是关于以机器可读的方式表达想法。这仍然是一个令人兴奋的前景:程序是将想法转化为现实,几乎不涉及任何物理实体。 + +程序员需要精通机器的语言,无论是真实的还是虚拟的,以及通过开发工具与该语言相关的抽象概念。学习许多不同的抽象概念非常重要,否则有些想法会变得极其难以表达。优秀的程序员需要能够跳出日常工作的框架,意识到其他语言在其他用途中的表达能力。这种能力总会在某个时刻得到回报。 + +除了与机器的沟通,程序员还需要与同行沟通。今天的大型项目更多地是社会性的事业,而不仅仅是编程艺术的应用。理解和表达超出机器可读抽象概念的内容非常重要。我认识的大多数优秀程序员都非常精通母语,通常还精通其他语言。这不仅关乎与他人的沟通:精通一门语言还能带来思维的清晰度,这在抽象问题时是不可或缺的。而这正是编程的核心所在。 + +除了与机器、自己和同行的沟通,一个项目还有许多利益相关者,大多数人的技术背景不同或根本没有技术背景。他们生活在测试、质量和部署中,生活在市场营销和销售中,他们是某个办公室(或商店或家庭)的终端用户。你需要理解他们和他们关心的问题。如果你不会说他们的语言——他们世界的语言,他们的领域语言,这几乎是不可能的。虽然你可能认为与他们的对话进行得很顺利,但他们可能并不这么认为。 + +如果你与会计师交谈,你需要对成本中心会计、绑定资本、使用资本等有基本的了解。如果你与市场营销人员或律师交谈,他们的行话和语言(以及他们的思维方式)应该对你来说很熟悉。所有这些特定领域的语言都需要项目中的某个人掌握——理想情况下是程序员。程序员最终负责通过计算机将想法变为现实。 + +当然,生活不仅仅是软件项目。正如[查理曼大帝](http://en.wikipedia.org/wiki/Charlemagne)所说,掌握另一门语言就是拥有另一个灵魂。对于你在软件行业之外的社交圈,掌握外语会让你受益匪浅。知道何时倾听而不是说话。知道大多数语言是无言的。 + +> *对于无法言说之事,必须保持沉默。* ——路德维希·维特根斯坦 + +作者:[克劳斯·马夸特](http://programmer.97things.oreilly.com/wiki/index.php/Klaus_Marquardt) \ No newline at end of file diff --git a/zh/thing_50/README.md b/zh/thing_50/README.md new file mode 100644 index 00000000..ab0c8ece --- /dev/null +++ b/zh/thing_50/README.md @@ -0,0 +1,31 @@ +# 学会估算 + +作为程序员,你需要能够向你的经理、同事和用户提供你所需执行任务的估算,以便他们能够合理地了解实现目标所需的时间、成本、技术和其他资源。 + +为了能够进行良好的估算,显然学习一些估算技巧非常重要。然而,首先需要了解什么是估算以及它们应该用于什么目的——尽管这看起来很奇怪,但许多开发人员和经理并不真正了解这一点。 + +以下项目经理和程序员之间的对话并不罕见: + +> *项目经理:* 你能给我估算一下开发 *xyz* 功能所需的时间吗? + +> *程序员:* 一个月。 + +> *项目经理:* 那太长了!我们只有一周的时间。 + +> *程序员:* 我至少需要三周。 + +> *项目经理:* 我最多只能给你两周。 + +> *程序员:* 成交! + +最终,程序员提出了一个与经理可接受的“估算”相符的数字。但由于这被视为程序员的估算,经理将要求程序员对此负责。要理解这段对话中的问题,我们需要三个定义——估算、目标和承诺: + +- **估算** 是对某事物的价值、数量、数量或范围的近似计算或判断。这个定义意味着估算是一个基于硬数据和先前经验的事实性衡量——在计算时必须忽略希望和愿望。该定义还意味着,既然是近似的,估算不可能精确,例如,一个开发任务不可能被估算为持续 234.14 天。 +- **目标** 是一个理想的业务目标的陈述,例如,“系统必须支持至少 400 个并发用户。” +- **承诺** 是承诺在某个日期或事件之前以一定的质量水平交付指定的功能。一个例子可能是“搜索功能将在产品的下一个版本中提供。” + +估算、目标和承诺是相互独立的,但目标和承诺应基于合理的估算。正如史蒂夫·麦康奈尔(Steve McConnell)所指出的,“软件估算的主要目的不是预测项目的结果;而是确定项目的目标是否足够现实,以便能够控制项目以实现这些目标。”因此,估算的目的是使适当的项目管理和规划成为可能,使项目利益相关者能够基于现实的目标做出承诺。 + +在上述对话中,经理实际上是在要求程序员基于他心中的未明说目标做出承诺,而不是提供估算。下次当你被要求提供估算时,确保所有相关人员都知道他们在谈论什么,这样你的项目将有更大的成功机会。现在是时候学习一些技巧了…… + +作者:[Giovanni Asproni](http://programmer.97things.oreilly.com/wiki/index.php/Giovanni_Asproni) \ No newline at end of file diff --git a/zh/thing_51/README.md b/zh/thing_51/README.md new file mode 100644 index 00000000..e27540d5 --- /dev/null +++ b/zh/thing_51/README.md @@ -0,0 +1,39 @@ +# 学习说“你好,世界” + +Paul Lee,用户名 leep,更常被称为 Hoppy,是当地编程问题的专家。我需要帮助。我走到 Hoppy 的桌前,问他能否帮我看看一些代码。 + +当然,Hoppy 说,拉把椅子过来。我小心翼翼地不碰倒他身后堆成金字塔的空可乐罐。 + +什么代码? + +在一个文件中的一个函数里,我说。 + +那我们来看看这个函数。Hoppy 把一本 K&R 的书挪到一边,把键盘推到我面前。 + +IDE 在哪里?显然 Hoppy 没有运行任何 IDE,只有一些我无法操作的编辑器。他把键盘拿回去。敲了几下键盘后,我们打开了文件——这是一个相当大的文件——然后看到了那个函数——这也是一个相当大的函数。他翻到我想要问的那个条件块。 + +如果 `x` 是负数,这个子句会做什么?我问。这肯定是错的。 + +我整个早上都在试图找到一种方法让 `x` 变成负数,但这个大文件中的大函数是一个大项目的一部分,重新编译*然后*重新运行我的实验的循环让我筋疲力尽。像 Hoppy 这样的专家难道不能直接告诉我答案吗? + +Hoppy 承认他也不确定。让我惊讶的是,他没有去拿 K&R。相反,他把代码块复制到一个新的编辑器缓冲区,重新缩进,把它包装成一个函数。不久之后,他编写了一个主函数,循环不断,提示用户输入值,将它们传递给函数,并打印出结果。他把缓冲区保存为一个新文件,tryit.c。所有这些我本可以自己完成,尽管可能不会这么快。但他的下一步非常简单,而且当时对我来说非常陌生: + +``` +$ cc tryit.c && ./a.out +``` + +看!他刚刚构思的实际程序现在已经运行起来了。我们试了几个值,确认了我的怀疑(所以我确实是对的!),然后他交叉检查了 K&R 的相关部分。我感谢了 Hoppy 并离开了,再次小心翼翼地不碰倒他的可乐罐金字塔。 + +回到自己的桌前,我关闭了我的 IDE。我已经习惯了在一个大产品中处理一个大项目,开始认为这就是我应该做的。一台通用计算机也可以做小任务。我打开一个文本编辑器,开始输入。 + +``` +#include + +int main() +{ + printf("你好,世界\n"); + return 0; +} +``` + +作者:[Thomas Guest](http://programmer.97things.oreilly.com/wiki/index.php/Thomas_Guest) \ No newline at end of file diff --git a/zh/thing_52/README.md b/zh/thing_52/README.md new file mode 100644 index 00000000..40577cea --- /dev/null +++ b/zh/thing_52/README.md @@ -0,0 +1,17 @@ +# 让你的项目自己说话 + +你的项目可能已经有一个版本控制系统。也许它连接到一个持续集成服务器,通过自动化测试来验证正确性。这很棒。 + +你可以将静态代码分析工具集成到持续集成服务器中,以收集代码指标。这些指标提供了关于代码特定方面的反馈,以及它们随时间的变化。当你安装代码指标时,总会有一条你不想跨越的红线。假设你从20%的测试覆盖率开始,并且永远不想低于15%。持续集成帮助你跟踪所有这些数字,但你仍然需要定期检查。想象一下,你可以将这个任务委托给项目本身,并依靠它在情况恶化时报告。 + +你需要给你的项目一个声音。这可以通过电子邮件或即时消息来完成,通知开发人员最新的数字下降或改进。但更有效的方法是在办公室中通过使用极端反馈设备(XFD)来体现项目。 + +XFD的想法是基于自动分析的结果驱动一个物理设备,如灯、便携式喷泉、玩具机器人,甚至是USB火箭发射器。每当你的限制被打破时,设备就会改变其状态。如果是灯,它会亮起来,明亮而明显。即使你匆忙出门回家,也不会错过这个消息。 + +根据极端反馈设备的类型,你可以听到构建失败,看到代码中的红色警告信号,甚至闻到代码的异味。如果你在一个分布式团队中工作,这些设备可以在不同的位置复制。你可以在项目经理的办公室放置一个交通灯,指示项目的整体健康状况。你的项目经理会感激的。 + +让你的创造力引导你选择一个合适的设备。如果你的文化比较极客,你可能会寻找方法为你的团队吉祥物配备无线电控制的玩具。如果你想要更专业的外观,可以投资于时尚的设计师灯。在互联网上搜索更多灵感。任何带有电源插头或遥控器的东西都有可能被用作极端反馈设备。 + +极端反馈设备充当了你项目的声音盒。项目现在与开发人员物理上共存,根据团队选择的规则抱怨或赞扬他们。你可以通过应用语音合成软件和一对扬声器进一步推动这种拟人化。现在你的项目真的自己说话了。 + +作者:[Daniel Lindner](http://programmer.97things.oreilly.com/wiki/index.php/Daniel_Lindner) \ No newline at end of file diff --git a/zh/thing_53/README.md b/zh/thing_53/README.md new file mode 100644 index 00000000..6c84719f --- /dev/null +++ b/zh/thing_53/README.md @@ -0,0 +1,52 @@ +# 链接器并不是一个神奇的程序 + +令人沮丧的是(就在我写这篇文章之前,这种情况又发生在我身上),许多程序员对从源代码到编译语言中的静态链接可执行文件的过程的看法是: + +1. 编辑源代码 +2. 将源代码编译成目标文件 +3. 发生了一些神奇的事情 +4. 运行可执行文件 + +当然,第3步就是链接步骤。为什么我会说这么离谱的话?我已经做了几十年的技术支持,并且一次又一次地收到以下问题: + +- 链接器说 `def` 被多次定义。 +- 链接器说 `abc` 是一个未解析的符号。 +- 为什么我的可执行文件这么大? + +接着通常是“我现在该怎么办?”通常伴随着“似乎”和“不知怎么的”这样的短语,以及一种完全困惑的氛围。正是“似乎”和“不知怎么的”表明链接过程被视为一个神奇的过程,大概只有巫师和术士才能理解。编译过程并不会引发这些短语,这意味着程序员通常理解编译器的工作原理,或者至少知道它们做了什么。 + +链接器是一个非常愚蠢、平凡、直接的程序。它所做的一切就是将目标文件的代码和数据段连接在一起,将符号的引用与其定义连接起来,从库中提取未解析的符号,并写出一个可执行文件。就是这样。没有咒语!没有魔法!编写链接器的繁琐之处通常在于解码和生成通常极其复杂的文件格式,但这并不会改变链接器的本质。 + +所以,假设链接器说 `def` 被多次定义。许多编程语言,如C、C++和D,都有声明和定义。声明通常放在头文件中,比如: + +``` +extern int iii; +``` + +这会生成对符号 `iii` 的外部引用。另一方面,定义实际上为符号分配了存储空间,通常出现在实现文件中,看起来像这样: + +``` +int iii = 3; +``` + +每个符号可以有多少个定义?就像电影《高地人》中一样,只能有一个。那么,如果 `iii` 的定义出现在多个实现文件中会发生什么? + +``` +// 文件 a.c +int iii = 3; +``` + +``` +// 文件 b.c +double iii(int x) { return 3.7; } +``` + +链接器会抱怨 `iii` 被多次定义。 + +不仅只能有一个定义,而且必须有一个定义。如果 `iii` 只作为声明出现,但从未定义过,链接器会抱怨 `iii` 是一个未解析的符号。 + +要确定可执行文件的大小,可以查看链接器可选生成的映射文件。映射文件只不过是可执行文件中所有符号及其地址的列表。这告诉你从库中链接了哪些模块,以及每个模块的大小。现在你可以看到膨胀来自哪里。通常会有一些库模块,你根本不知道为什么它们会被链接进来。要弄清楚这一点,可以暂时从库中移除可疑的模块,然后重新链接。生成的未定义符号错误将指示谁在引用该模块。 + +尽管有时并不立即明显为什么你会得到特定的链接器消息,但链接器并没有什么神奇之处。机制是直接的;你只需要在每种情况下弄清楚细节。 + +作者:[Walter Bright](http://creativecommons.org/licenses/by/3.0/us/) \ No newline at end of file diff --git a/zh/thing_54/README.md b/zh/thing_54/README.md new file mode 100644 index 00000000..10c5d3c8 --- /dev/null +++ b/zh/thing_54/README.md @@ -0,0 +1,33 @@ +# 临时解决方案的长寿性 + +我们为什么要创建临时解决方案? + +通常是为了解决一些紧迫的问题。可能是开发团队内部的问题,比如填补工具链中的某些空缺的工具。也可能是外部问题,对最终用户可见,比如解决某些功能缺失的变通方法。 + +在大多数系统和团队中,你会发现一些与系统有些脱节的软件,这些软件被认为是未来某个时候需要修改的草稿,它们没有遵循塑造其余代码的标准和指南。不可避免地,你会听到开发人员抱怨这些软件。它们被创建的原因多种多样,但临时解决方案成功的关键很简单:它是有用的。 + +然而,临时解决方案会获得惯性(或动力,取决于你的观点)。因为它们已经存在,最终是有用的并且被广泛接受,所以没有立即需要做其他事情的必要。每当利益相关者必须决定哪种行动能带来最大价值时,会有许多行动排在将临时解决方案正确集成之前。为什么?因为它已经存在,它能工作,并且被接受。唯一被认为的缺点是它没有遵循所选择的标准和指南——除了少数小众市场外,这并不被认为是一个重要的力量。 + +因此,临时解决方案会一直存在。永远存在。 + +如果临时解决方案出现问题,不太可能会有更新计划将其提升到符合生产质量的标准。该怎么办?通常,对临时解决方案进行快速的临时更新就能解决问题。而且很可能会受到欢迎。它展示了与最初的临时解决方案相同的优势……只是更加更新。 + +这是个问题吗? + +答案取决于你的项目,以及你对生产代码标准的个人投入。当系统中包含太多临时解决方案时,其熵或内部复杂性会增加,可维护性会降低。然而,这可能不是首先要问的问题。记住,我们讨论的是一个解决方案。它可能不是你首选的解决方案——它不太可能是任何人的首选解决方案——但重新设计这个解决方案的动力很弱。 + +那么,如果我们发现问题,我们能做什么? + +1. 首先避免创建临时解决方案。 +2. 改变影响项目经理决策的力量。 +3. 保持现状。 + +让我们更仔细地研究这些选项: + +1. 避免在大多数地方并不奏效。有一个实际问题需要解决,而标准被证明过于严格。你可能会花一些精力尝试改变标准。这是一项光荣但乏味的努力……而且这种改变不会及时对你手头的问题产生影响。 +2. 这些力量根植于项目文化中,抵制意志的改变。在非常小的项目中可能会成功——尤其是如果只有你一个人——而你恰好在不事先询问的情况下清理了混乱。如果项目非常混乱,明显停滞不前,并且清理时间被普遍接受,这也可能会成功。 +3. 如果前一个选项不适用,现状会自动适用。 + +你会创建许多解决方案,其中一些是临时的,大多数是有用的。克服临时解决方案的最佳方法是使它们变得多余,提供一个更优雅和有用的解决方案。愿你被赐予[宁静](http://en.wikipedia.org/wiki/Serenity_prayer)去接受你无法改变的事情,勇气去改变你能改变的事情,以及智慧去分辨两者的区别。 + +作者:[Klaus Marquardt](http://programmer.97things.oreilly.com/wiki/index.php/Klaus_Marquardt) \ No newline at end of file diff --git a/zh/thing_55/README.md b/zh/thing_55/README.md new file mode 100644 index 00000000..70aa4c01 --- /dev/null +++ b/zh/thing_55/README.md @@ -0,0 +1,16 @@ +# 让接口易于正确使用,难以错误使用 + +软件开发中最常见的任务之一就是接口规范。接口出现在最高层次的抽象(用户界面)中,最低层次的抽象(函数接口)中,以及介于两者之间的层次(类接口、库接口等)中。无论你是与最终用户一起指定他们如何与系统交互,还是与开发人员一起指定API,或者声明类的私有函数,接口设计都是你工作的重要组成部分。如果你做得好,你的接口将易于使用,并能提高他人的生产力。如果你做得不好,你的接口将成为沮丧和错误的来源。 + +好的接口具有以下特点: + +- **易于正确使用。** 使用设计良好的接口的人几乎总是能正确使用接口,因为这是最省力的路径。在图形用户界面(GUI)中,他们几乎总是点击正确的图标、按钮或菜单项,因为这是显而易见且容易做的事情。在API中,他们几乎总是传递正确的参数和值,因为这是最自然的方式。对于易于正确使用的接口,*事情就是能正常工作。* +- **难以错误使用。** 好的接口会预见到人们可能犯的错误,并使这些错误难以——理想情况下是不可能——发生。例如,GUI可能会禁用或删除在当前上下文中没有意义的命令,或者API可能会通过允许以任何顺序传递参数来消除参数顺序问题。 + +设计易于正确使用的接口的一个好方法是在它们存在之前就进行演练。在编写任何底层代码之前,模拟一个GUI——可能是在白板上或使用桌子上的索引卡片——并与之互动。在声明函数之前编写API调用。遍历常见的使用场景,并指定你*希望*接口如何行为。你*希望*能够点击什么?你*希望*能够传递什么?易于使用的接口感觉很自然,因为它们让你做你想做的事情。如果你从用户的角度开发这些接口,你更有可能想出这样的接口。(这种视角是测试驱动编程的优势之一。) + +让接口难以错误使用需要两件事。首先,你必须预见到用户可能犯的错误,并找到防止这些错误的方法。其次,你必须观察在早期发布期间接口是如何被误用的,并修改接口——是的,修改接口!——以防止这些错误。防止错误使用的最佳方法是使这种使用变得不可能。如果用户一直想要撤销一个不可撤销的操作,尝试使该操作可撤销。如果他们一直向API传递错误的值,尽你所能修改API以接受用户想要传递的值。 + +最重要的是,记住接口的存在是为了方便用户,而不是实现者。 + +作者:[Scott Meyers](http://programmer.97things.oreilly.com/wiki/index.php/Scott_Meyers) \ No newline at end of file diff --git a/zh/thing_56/README.md b/zh/thing_56/README.md new file mode 100644 index 00000000..a7f24624 --- /dev/null +++ b/zh/thing_56/README.md @@ -0,0 +1,19 @@ +# 让不可见的事物更加可见 + +不可见性的许多方面被恰当地赞誉为需要坚持的软件原则。我们的术语中充满了不可见性的隐喻——机制透明和信息隐藏就是其中两个。借用道格拉斯·亚当斯的话来说,软件及其开发过程可以说是*大部分不可见的*: + +- 源代码没有固有的存在,没有固有的行为,也不遵循物理定律。当你将其加载到编辑器中时,它是可见的,但关闭编辑器后它就消失了。如果你思考得太久,就像一棵树倒下时没有人听到一样,你可能会开始怀疑它是否真的存在。 +- 一个运行的应用程序有存在和行为,但它不会揭示构建它的源代码。谷歌的主页非常简洁;背后的运作肯定是复杂的。 +- 如果你完成了90%,却一直卡在调试最后10%的过程中,那么你并没有完成90%,对吧?修复错误并不是在取得进展。你并不是为了调试而获得报酬。调试是浪费。让浪费更加可见是好事,这样你可以看清它的本质,并开始思考如何从一开始就避免产生它。 +- 如果你的项目表面上进展顺利,但一周后却晚了六个月,那么你就有问题了,最大的问题可能不是晚了六个月,而是那些强大到足以隐藏六个月延迟的不可见力场!缺乏可见的进展等同于没有进展。 + +不可见性可能是危险的。当你有一些具体的东西来支撑你的思考时,你会更清晰地思考。当你能够看到它们并看到它们不断变化时,你会更好地管理它们: + +- 编写单元测试提供了关于代码单元是否易于单元测试的证据。它有助于揭示代码是否具备你希望它展现的开发质量,例如低耦合和高内聚。 +- 运行单元测试提供了关于代码行为的证据。它有助于揭示应用程序是否具备你希望它展现的运行时质量,例如健壮性和正确性。 +- 使用公告板和卡片使进展可见且具体。任务可以被视为*未开始*、*进行中*或*已完成*,而无需参考隐藏的项目管理工具,也无需向程序员索取虚构的状态报告。 +- 进行增量开发通过增加开发证据的频率来提高开发进展(或缺乏进展)的可见性。完成可发布的软件揭示了现实;估算则不能。 + +最好在开发软件时提供大量定期的可见证据。可见性让人相信进展是真实的而非幻觉,是刻意的而非无意的,是可重复的而非偶然的。 + +作者:[Jon Jagger](http://programmer.97things.oreilly.com/wiki/index.php/Jon_Jagger) \ No newline at end of file diff --git a/zh/thing_57/README.md b/zh/thing_57/README.md new file mode 100644 index 00000000..e8b15db0 --- /dev/null +++ b/zh/thing_57/README.md @@ -0,0 +1,19 @@ +# 消息传递在并行系统中带来更好的可扩展性 + +程序员从学习计算机的一开始就被教导并发——尤其是并行性,并发的一个特殊子集——是困难的,只有最优秀的人才能希望把它做好,而即使是他们也常常出错。人们总是非常关注线程、信号量、监视器,以及如何使变量的并发访问变得线程安全。 + +确实,存在许多难题,它们可能非常难以解决。但问题的根源是什么?共享内存。几乎所有人们反复讨论的并发问题都与共享可变内存的使用有关:竞态条件、死锁、活锁等。答案似乎显而易见:要么放弃并发,要么避免共享内存! + +放弃并发几乎肯定不是一个选项。计算机几乎每季度都有越来越多的核心,因此利用真正的并行性变得越来越重要。我们不能再依赖处理器时钟速度的不断提高来提高应用程序性能。只有通过利用并行性,应用程序的性能才能提高。显然,不提高性能也是一种选择,但这不太可能被用户接受。 + +那么,我们能避免共享内存吗?当然可以。 + +我们可以使用进程和消息传递,而不是使用线程和共享内存作为我们的编程模型。这里的进程仅仅意味着一个受保护的独立状态和执行代码,不一定是操作系统进程。像Erlang(以及之前的occam)这样的语言已经表明,进程是编程并发和并行系统的一个非常成功的机制。这样的系统没有共享内存、多线程系统所具有的所有同步压力。此外,还有一个形式化模型——通信顺序进程(CSP)——可以作为此类系统工程的一部分应用。 + +我们可以更进一步,引入数据流系统作为一种计算方式。在数据流系统中,没有显式编程的控制流。相反,建立了一个由数据路径连接的操作符的有向图,然后将数据输入系统。评估由系统中数据的准备情况控制。绝对没有同步问题。 + +尽管如此,像C、C++、Java、Python和Groovy这样的语言是系统开发的主要语言,所有这些语言都被呈现给程序员,作为开发共享内存、多线程系统的语言。那么我们能做些什么呢?答案是使用——或者如果它们不存在,就创建——提供进程模型和消息传递的库和框架,避免使用共享可变内存。 + +总的来说,不使用共享内存编程,而是使用消息传递,可能是实现利用计算机硬件中普遍存在的并行性的系统的最成功的方式。也许奇怪的是,尽管进程作为并发单位早于线程,但未来似乎在于使用线程来实现进程。 + +作者:[Russel Winder](http://programmer.97things.oreilly.com/wiki/index.php/Russel_Winder) \ No newline at end of file diff --git a/zh/thing_58/README.md b/zh/thing_58/README.md new file mode 100644 index 00000000..fbecccc5 --- /dev/null +++ b/zh/thing_58/README.md @@ -0,0 +1,23 @@ +# 给未来的信息 + +也许是因为大多数程序员都是聪明人,但在我多年教学和与程序员并肩工作的经历中,似乎大多数人都认为,既然他们正在解决的问题很困难,那么解决方案对其他人(甚至可能是他们自己在代码编写几个月后)来说也应该同样难以理解和维护。 + +我记得有一次,我的数据结构课上的学生乔来找我展示他写的代码。“你肯定猜不到它是干什么的!”他得意地说。 + +“你说得对,”我同意道,没有花太多时间看他的例子,而是在思考如何传达一个重要信息。“我相信你在这上面花了很多功夫。不过,我在想,你是否忘记了一些重要的事情。乔,你不是有个弟弟吗?” + +“是啊!当然有!菲尔!他在你的入门课上。他也在学编程!”乔自豪地宣布。 + +“那太好了,”我回答。“我在想他能不能读懂这段代码。” + +“不可能!”乔说。“这很难的!” + +“假设一下,”我建议道,“如果这是实际的工作代码,几年后菲尔被雇来做一个维护更新。你为他做了什么?”乔只是眨着眼睛盯着我。“我们知道菲尔很聪明,对吧?”乔点点头。“虽然我不想这么说,但我也挺聪明的!”乔笑了。“所以,如果我不能轻易理解你在这里做了什么,而你那个非常聪明的弟弟也可能会对这段代码感到困惑,那你写的代码意味着什么呢?”乔似乎开始以不同的眼光看待他的代码了。“这样吧,”我用最友好的导师语气建议道,“把你写的每一行代码都当作给未来的某个人——可能是你弟弟——的信息。假装你在向这个聪明的人解释如何解决这个棘手的问题。 + +“这是你想象的吗?未来的聪明程序员看到你的代码会说,‘哇!这太棒了!我完全理解这里做了什么,而且我对这段优雅——不,等等——这段美丽的代码感到惊叹。我要给我的团队其他人看看。这是一件杰作!’ + +“乔,你认为你能写出解决这个难题的代码,并且让它美得像一首歌吗?是的,就像一首萦绕心头的旋律。我认为任何能想出你这里这个非常困难的解决方案的人,也能写出一些美丽的东西。嗯……我在想我是不是应该开始给代码的美感打分?你觉得呢,乔?” + +乔拿起他的作品,看着我,脸上露出一丝微笑。“我明白了,教授,我要去为菲尔让这个世界变得更好了。谢谢。” + +作者:[琳达·瑞辛](http://programmer.97things.oreilly.com/wiki/index.php/Linda_Rising) \ No newline at end of file diff --git a/zh/thing_59/README.md b/zh/thing_59/README.md new file mode 100644 index 00000000..f0e76129 --- /dev/null +++ b/zh/thing_59/README.md @@ -0,0 +1,51 @@ +# 多态性的缺失机会 + +多态性是面向对象编程(OO)中的核心思想之一。这个词源自希腊语,意思是“多”(*poly*)种“形式”(*morph*)。在编程的上下文中,多态性指的是某一类对象或方法的多种形式。但多态性不仅仅是关于替代实现。如果谨慎使用,多态性可以创建微小的局部执行上下文,使我们无需冗长的 *if-then-else* 块即可工作。处于上下文中允许我们直接做正确的事情,而处于上下文之外则迫使我们重建上下文,以便我们能够做正确的事情。通过谨慎使用替代实现,我们可以捕获上下文,从而帮助我们生成更少、更易读的代码。这一点最好通过一些代码来展示,比如以下(不切实际的)简单的购物车: + +```java +public class ShoppingCart { + private ArrayList cart = new ArrayList(); + public void add(Item item) { cart.add(item); } + public Item takeNext() { return cart.remove(0); } + public boolean isEmpty() { return cart.isEmpty(); } +} +``` + +假设我们的网店提供可以下载的商品和需要邮寄的商品。让我们构建另一个对象来支持这些操作: + +```java +public class Shipping { + public boolean ship(Item item, SurfaceAddress address) { ... } + public boolean ship(Item item, EMailAddress address) { ... } +} +``` + +当客户完成结账时,我们需要发货: + +```java +while (!cart.isEmpty()) { + shipping.ship(cart.takeNext(), ???); +} +``` + +这里的 *???* 参数并不是某种新的花哨的 Elvis 操作符,而是在问:我应该通过电子邮件还是普通邮件发送这个商品?回答这个问题所需的上下文已经不存在了。我们可以用一个布尔值或枚举来捕获发货方式,然后使用 *if-then-else* 来填充缺失的参数。另一种解决方案是创建两个类,它们都继承自 `Item`。让我们称它们为 `DownloadableItem` 和 `SurfaceItem`。现在让我们编写一些代码。我将把 `Item` 提升为一个支持单一方法 `ship` 的接口。为了发货购物车中的内容,我们将调用 `item.ship(shipper)`。类 `DownloadableItem` 和 `SurfaceItem` 都将实现 `ship` 方法。 + +```java +public class DownloadableItem implements Item { + public boolean ship(Shipping shipper) { + shipper.ship(this, customer.getEmailAddress()); + } +} + +public class SurfaceItem implements Item { + public boolean ship(Shipping shipper) { + shipper.ship(this, customer.getSurfaceAddress()); + } +} +``` + +在这个例子中,我们将与 `Shipping` 交互的责任委托给了每个 `Item`。由于每个商品都知道如何最好地发货,这种安排使我们能够继续工作,而无需使用 *if-then-else*。这段代码还展示了两种经常配合使用的模式:命令模式和双重分派模式。这些模式的有效使用依赖于对多态性的谨慎使用。当这种情况发生时,我们代码中的 *if-then-else* 块的数量将会减少。 + +虽然在某些情况下使用 *if-then-else* 比多态性更实用,但更多情况下,更具多态性的编码风格将产生更小、更易读且更不易出错的代码库。错失的机会的数量可以通过我们代码中的 *if-then-else* 语句的简单计数来衡量。 + +作者:[Kirk Pepperdine](http://programmer.97things.oreilly.com/wiki/index.php/Kirk_Pepperdine) \ No newline at end of file diff --git a/zh/thing_60/README.md b/zh/thing_60/README.md new file mode 100644 index 00000000..939c1c22 --- /dev/null +++ b/zh/thing_60/README.md @@ -0,0 +1,17 @@ +# 奇闻轶事:测试员是你的朋友 + +无论他们自称是*质量保证*还是*质量控制*,许多程序员都称他们为*麻烦制造者*。根据我的经验,程序员通常与测试他们软件的人之间存在一种对立关系。“他们太挑剔了”和“他们希望一切都完美”是常见的抱怨。听起来熟悉吗? + +我不确定为什么,但我对测试员的看法总是与众不同。也许是因为我第一份工作中的“测试员”是公司秘书。玛格丽特是一位非常友善的女士,她负责维持办公室的运转,并试图教几个年轻程序员如何在客户面前表现得专业。她还有一种天赋,能在瞬间发现任何错误,无论这些错误多么隐蔽。 + +那时,我正在开发一个由一位自认为是程序员的会计师编写的程序。不用说,这个程序存在一些严重的问题。当我以为我已经把某一部分理顺了,玛格丽特会尝试使用它,而往往在几次按键后,它就会以某种新的方式失败。这有时令人沮丧和尴尬,但她是一个非常愉快的人,我从未想过因为她让我看起来糟糕而责怪她。最终有一天,玛格丽特能够顺利地启动程序,输入发票,打印它,然后关闭它。我非常激动。更棒的是,当我们在客户的机器上安装它时,一切都运行良好。他们从未看到任何问题,因为玛格丽特已经帮助我先发现并修复了这些问题。 + +所以这就是为什么我说测试员是你的朋友。你可能会认为测试员通过报告琐碎的问题让你看起来糟糕。但当客户因为没有被那些质量控制让你修复的“小问题”困扰而感到高兴时,你就显得非常出色。明白我的意思吗? + +想象一下:你正在测试一个使用“突破性的人工智能算法”来发现和修复并发问题的实用程序。你启动它,立即注意到他们在启动屏幕上拼错了“intelligence”。有点不吉利,但这只是一个拼写错误,对吧?然后你注意到配置屏幕在应该使用单选按钮的地方使用了复选框,而且一些键盘快捷键不起作用。现在,这些都不是什么大问题,但随着错误的累积,你开始怀疑程序员的能力。如果他们连简单的事情都做不好,他们的AI真的能发现并修复像并发问题这样棘手的东西吗? + +他们可能是天才,专注于让AI变得极其出色,以至于没有注意到那些琐碎的事情。如果没有“挑剔的测试员”指出问题,你最终发现了它们。现在你开始质疑程序员的能力。 + +所以,尽管听起来很奇怪,那些似乎决心暴露你代码中每一个小错误的测试员真的是你的朋友。 + +作者:[Burk Hufnagel](http://programmer.97things.oreilly.com/wiki/index.php/BurkHufnagel) \ No newline at end of file diff --git a/zh/thing_61/README.md b/zh/thing_61/README.md new file mode 100644 index 00000000..457905e5 --- /dev/null +++ b/zh/thing_61/README.md @@ -0,0 +1,15 @@ +# 单一二进制文件 + +我见过几个项目,在构建过程中会重写代码的某些部分,以便为每个目标环境生成定制的二进制文件。这总是让事情变得比应有的更复杂,并引入了团队在每个安装环境中可能没有一致版本的风险。至少,它涉及构建多个几乎相同的软件副本,每个副本都必须部署到正确的位置。这意味着比必要更多的移动部件,也就意味着更多的出错机会。 + +我曾经在一个团队工作,每次属性更改都必须经过完整的构建周期,因此每当测试人员需要进行小的调整时,他们就得等待(我有没有提到构建时间太长?)。我也曾在一个团队工作,系统管理员坚持在生产环境中从头开始重建(使用与我们相同的脚本),这意味着我们无法证明生产环境中的版本是经过测试的版本。诸如此类。 + +规则很简单:*构建一个单一的二进制文件,你可以在发布管道的所有阶段中识别和推广它。* 将环境特定的细节保留在环境中。这可能意味着将它们保存在组件容器中、已知文件中或路径中。 + +如果你的团队有一个代码混淆的构建过程,或者将所有目标设置与代码一起存储,这表明没有人足够仔细地思考设计,以区分应用程序的核心功能和特定于平台的功能。或者更糟:团队知道该怎么做,但无法优先考虑进行更改的工作。 + +当然,也有例外:你可能正在为资源约束显著不同的目标构建,但这并不适用于我们大多数编写“从数据库到屏幕再返回”应用程序的人。或者,你可能正在处理一些遗留的混乱问题,这些问题目前太难修复。在这种情况下,你必须逐步推进——但要尽快开始。 + +还有一件事:也要对环境信息进行版本控制。没有什么比破坏环境配置却无法弄清楚发生了什么变化更糟糕的了。环境信息应该与代码分开进行版本控制,因为它们会以不同的速度和不同的原因发生变化。一些团队为此使用分布式版本控制系统(如bazaar和git),因为它们使得在生产环境中进行的更改——这是不可避免的——更容易推送到仓库中。 + +作者:[Steve Freeman](http://programmer.97things.oreilly.com/wiki/index.php/Steve_Freeman) \ No newline at end of file diff --git a/zh/thing_62/README.md b/zh/thing_62/README.md new file mode 100644 index 00000000..ff5c16b5 --- /dev/null +++ b/zh/thing_62/README.md @@ -0,0 +1,13 @@ +# 只有代码会告诉你真相 + +程序的最终语义是由运行的代码给出的。如果代码只有二进制形式,那么阅读起来将会非常困难!然而,如果你正在编写自己的程序、参与典型的商业软件开发、开源项目,或者使用动态解释型语言编写代码,源代码应该是可用的。通过查看源代码,程序的含义应该是显而易见的。要知道一个程序在做什么,源代码是你唯一可以确信的东西。即使是最准确的需求文档也无法告诉你全部真相:它并不包含程序实际执行的详细故事,而只是需求分析师的高层意图。设计文档可能捕捉了计划中的设计,但它缺乏实现的必要细节。这些文档可能与当前的实现不同步……或者可能已经丢失。或者从一开始就没有写下来。源代码可能是唯一剩下的东西。 + +考虑到这一点,问问自己,你的代码是否清晰地告诉了你或其他程序员它在做什么? + +你可能会说:“哦,我的注释会告诉你所有你需要知道的东西。”但请记住,注释并不是运行的代码。它们可能和其他形式的文档一样是错误的。有一种传统观点认为注释是无条件的好东西,因此一些程序员不加思索地写越来越多的注释,甚至重复解释代码中已经显而易见的琐碎细节。这是澄清代码的错误方式。如果你的代码需要注释,考虑重构它,使其不需要注释。冗长的注释会占用屏幕空间,甚至可能被你的IDE自动隐藏。如果你需要解释某个更改,请在版本控制系统的提交信息中说明,而不是在代码中。 + +你能做些什么来让你的代码尽可能清晰地表达真相呢?努力为代码取好名字。根据功能的内聚性来组织代码,这也有助于命名。解耦你的代码以实现正交性。编写自动化测试来解释预期的行为并检查接口。当你学会如何编写更简单、更好的解决方案时,无情地进行重构。让你的代码尽可能简单易读。 + +像对待其他作品一样对待你的代码,比如一首诗、一篇文章、一篇公开博客或一封重要的电子邮件。精心雕琢你所表达的内容,使其能够完成它应该做的事情,并尽可能直接地传达它在做什么,这样即使你不在场,它仍然能传达你的意图。记住,有用的代码的使用时间往往比预期的要长得多。维护程序员会感谢你。而且,如果你是一名维护程序员,而你正在处理的代码不容易传达真相,请主动应用上述准则。在代码中建立一些理智,并保持你自己的理智。 + +作者:[Peter Sommerlad](http://programmer.97things.oreilly.com/wiki/index.php/Peter_Sommerlad) \ No newline at end of file diff --git a/zh/thing_63/README.md b/zh/thing_63/README.md new file mode 100644 index 00000000..4ade5906 --- /dev/null +++ b/zh/thing_63/README.md @@ -0,0 +1,15 @@ +# 拥有(并重构)构建过程 + +通常,那些在编码实践上高度自律的团队往往会忽视构建脚本,要么是因为他们认为构建脚本只是一个不重要的细节,要么是因为他们担心构建脚本复杂,需要由发布工程的专家来维护。不可维护的构建脚本,包含重复和错误,会导致与糟糕代码相同程度的问题。 + +为什么那些自律且技术娴熟的开发者会将构建视为次要工作的一个理由是,构建脚本通常是用与源代码不同的语言编写的。另一个理由是,构建并不是真正的“代码”。这些理由与大多数软件开发人员喜欢学习新语言的现实相悖,而且构建是为开发人员和最终用户创建可执行工件的过程。没有构建,代码是无用的,而构建定义了应用程序的组件架构。构建是开发过程中不可或缺的一部分,关于构建过程的决策可以使代码和编码变得更简单。 + +使用错误习惯编写的构建脚本难以维护,更重要的是,难以改进。值得花一些时间来了解如何正确地进行更改。当应用程序使用错误版本的依赖项构建时,或者当构建时配置错误时,可能会出现错误。 + +传统上,测试总是留给“质量保证”团队。我们现在意识到,在编码时进行测试是能够可靠地交付价值的必要条件。同样,构建过程需要由开发团队拥有。 + +理解构建可以简化整个开发生命周期并降低成本。一个易于执行的构建可以让新开发人员快速轻松地开始工作。在构建中自动化配置可以使你在多人协作的项目中获得一致的结果,避免“它在我这里能运行”的对话。许多构建工具允许你运行代码质量报告,让你及早发现潜在问题。通过花时间了解如何使构建成为你自己的,你可以帮助自己和团队中的其他人。你可以专注于编码功能,使利益相关者受益,并使工作更加愉快。 + +学习足够的构建过程,以了解何时以及如何进行更改。构建脚本是代码。它们太重要了,不能交给别人,如果仅仅是因为应用程序在构建之前是不完整的。编程工作在我们交付可运行的软件之前是不完整的。 + +作者:[Steve Berczuk](http://programmer.97things.oreilly.com/wiki/index.php/Steve_Berczuk) \ No newline at end of file diff --git a/zh/thing_64/README.md b/zh/thing_64/README.md new file mode 100644 index 00000000..5c4c4287 --- /dev/null +++ b/zh/thing_64/README.md @@ -0,0 +1,25 @@ +# 结对编程与感受心流 + +想象一下,你完全沉浸在你正在做的事情中——专注、投入、全神贯注。你可能已经忘记了时间。你可能会感到快乐。你正在体验心流。对于一个开发团队来说,实现并保持心流是困难的,因为有太多的干扰、互动和其他分心的事情,很容易打破这种状态。 + +如果你已经实践过结对编程,你可能熟悉结对如何促进心流。如果你还没有实践过,我们希望用我们的经验来激励你现在就开始!要在结对编程中取得成功,团队成员个人和整个团队都需要付出一些努力。 + +作为团队成员,要对经验不如你丰富的开发者保持耐心。面对你对更有经验的开发者感到畏惧的恐惧。意识到人们是不同的,并珍视这种差异。了解自己的优势和劣势,以及其他团队成员的优势和劣势。你可能会惊讶于你能从同事那里学到多少东西。 + +作为团队,引入结对编程以促进技能和知识在整个项目中的分布。你们应该成对解决任务,并经常轮换结对和任务。就轮换规则达成一致。必要时将规则搁置或调整。我们的经验是,你不一定需要在将任务轮换给另一对之前完成任务。中断任务并将其传递给另一对可能听起来违反直觉,但我们发现这是有效的。 + +有许多情况下心流可能会被打断,但结对编程可以帮助你保持心流: + +- **减少“卡车因素”**:这是一个稍微病态的思维实验,但你的团队中有多少人必须被卡车撞到,团队才能无法完成最终交付?换句话说,你的交付对某些团队成员的依赖程度如何?知识是特权还是共享的?如果你一直在结对之间轮换任务,总有其他人拥有知识并可以完成工作。你的团队的心流不会受到“卡车因素”的影响。 + +- **有效解决问题**:如果你正在进行结对编程并遇到一个具有挑战性的问题,你总是有人可以讨论。这样的对话比你自己卡住时更有可能打开可能性。随着工作的轮换,你的解决方案将被下一对重新审视和考虑,所以如果你最初没有选择最优解也没关系。 + +- **顺利集成**:如果你当前的任务涉及调用另一段代码,你希望方法的名称、文档和测试足够描述性,以便你掌握它的功能。如果没有,与参与编写该代码的开发者结对将为你提供更好的概览,并更快地集成到你自己的代码中。此外,你可以利用讨论的机会改进命名、文档和测试。 + +- **减轻干扰**:如果有人过来问你问题,或者你的电话响了,或者你必须回复一封紧急邮件,或者你必须参加会议,你的结对编程伙伴可以继续编码。当你回来时,你的伙伴仍然处于心流状态,你会很快赶上并重新加入他们。 + +- **让新团队成员快速上手**:通过结对编程和适当的结对和任务轮换,新成员可以快速了解代码和其他团队成员。 + +心流让你变得非常高效。但它也很脆弱。尽你所能去获得它,并在你拥有它时紧紧抓住它! + +作者:[Gudny Hauknes](http://programmer.97things.oreilly.com/wiki/index.php/Gudny_Hauknes)、[Ann Katrin Gagnat](http://programmer.97things.oreilly.com/wiki/index.php/Ann_Katrin_Gagnat) 和 Kari Røssland \ No newline at end of file diff --git a/zh/thing_65/README.md b/zh/thing_65/README.md new file mode 100644 index 00000000..8efd875a --- /dev/null +++ b/zh/thing_65/README.md @@ -0,0 +1,31 @@ +# 优先使用领域特定类型而非原始类型 + +1999年9月23日,价值3.276亿美元的火星气候轨道器在进入火星轨道时失联,原因是地球上的一个软件错误。这个错误后来被称为*公制混淆*。地面站软件使用的是磅,而航天器预期的是牛顿,导致地面站低估了航天器推进器的功率,误差达到了4.45倍。 + +这是许多软件故障中的一个例子,如果应用了更强且更领域特定的类型系统,这些故障本可以避免。这也是Ada语言中许多特性背后的一个例证,Ada的主要设计目标之一是实现嵌入式安全关键软件。Ada具有强类型系统,并对原始类型和用户定义类型进行静态检查: + +```ada +type Velocity_In_Knots is new Float range 0.0 .. 500.00; + +type Distance_In_Nautical_Miles is new Float range 0.0 .. 3000.00; + +Velocity: Velocity_In_Knots; + +Distance: Distance_In_Nautical_Miles; + +Some_Number: Float; + +Some_Number:= Distance + Velocity; -- 编译器会捕获这个类型错误。 +``` + +在要求不那么严格的领域中,开发者也可以从应用更多领域特定类型中受益,否则他们可能会继续使用语言及其库提供的原始数据类型,如字符串和浮点数。在Java、C++、Python和其他现代语言中,抽象数据类型被称为类。使用诸如`Velocity_In_Knots`和`Distance_In_Nautical_Miles`这样的类,可以在代码质量方面带来很多好处: + +- 代码变得更易读,因为它表达了领域的概念,而不仅仅是浮点数或字符串。 +- 代码变得更易测试,因为代码封装了易于测试的行为。 +- 代码促进了跨应用程序和系统的重用。 + +这种方法对于静态类型语言和动态类型语言的用户同样有效。唯一的区别是,使用静态类型语言的开发者可以从编译器获得一些帮助,而使用动态类型语言的开发者则更可能依赖他们的单元测试。检查的方式可能不同,但动机和表达风格是相同的。 + +结论是,为了开发高质量的软件,开始探索领域特定类型是值得的。 + +作者:[Einar Landre](http://programmer.97things.oreilly.com/wiki/index.php/Einar_Landre) \ No newline at end of file diff --git a/zh/thing_66/README.md b/zh/thing_66/README.md new file mode 100644 index 00000000..24dc0f54 --- /dev/null +++ b/zh/thing_66/README.md @@ -0,0 +1,27 @@ +# 预防错误 + +错误信息是用户与系统之间最重要的交互之一。它们通常发生在用户与系统之间的沟通接近崩溃点时。 + +人们很容易认为错误是由用户的错误输入引起的。但人们会以可预测的、系统性的方式犯错。因此,你可以像调试其他系统组件之间的通信一样,调试用户与系统之间的通信。 + +例如,假设你希望用户输入一个在允许范围内的日期。与其让用户输入任意日期,不如提供一个仅显示允许日期的列表或日历控件。这样可以完全消除用户输入超出范围日期的可能性。 + +格式错误是另一个常见问题。例如,如果用户面对一个日期输入框,并输入了一个明确的日期,如“2012年7月29日”,仅仅因为它不符合首选格式(如“DD/MM/YYYY”)就拒绝它是不可取的。更糟糕的是,拒绝“29 / 07 / 2012”因为它包含额外的空格——这种问题尤其难以让用户理解,因为日期看起来符合所需的格式。 + +这种错误的发生是因为拒绝日期比解析三四种最常见的日期格式更容易。这类小错误会导致用户沮丧,进而导致用户注意力不集中,从而引发更多错误。相反,应该尊重用户输入信息的偏好,而不是数据。 + +避免格式错误的另一种方法是提供提示——例如,在输入框内显示所需格式的标签(“DD/MM/YYYY”)。另一种提示可能是将输入框分为三个文本框,分别用于输入两位、两位和四位字符。 + +提示与说明不同:提示通常是简短的暗示;说明则较为冗长。提示出现在交互点;说明出现在交互点之前。提示提供上下文;说明则规定使用方法。 + +一般来说,说明在预防错误方面效果不佳。用户倾向于假设界面会按照他们过去的经验工作(“当然每个人都知道‘2012年7月29日’是什么意思?”)。因此,说明往往不会被阅读。提示则能引导用户远离错误。 + +避免错误的另一种方法是提供默认值。例如,用户通常输入与*今天*、*明天*、*我的生日*、*我的截止日期*或*上次使用此表单时输入的日期*相对应的值。根据上下文,其中一个可能是作为智能默认值的好选择。 + +无论错误的原因是什么,系统都应该对错误保持宽容。你可以通过为所有操作提供多级*撤销*功能来实现这一点——特别是那些可能破坏或修改用户数据的操作。 + +记录和分析*撤销*操作还可以突出显示界面在哪里引导用户犯下无意识的错误,例如持续点击“错误”的按钮。这些错误通常是由误导性的提示或交互序列引起的,你可以重新设计这些提示或序列以防止进一步的错误。 + +无论你采取哪种方法,大多数错误都是系统性的——是用户与软件之间误解的结果。理解用户如何思考、解释信息、做出决策和输入数据,将帮助你调试软件与用户之间的交互。 + +作者:[Giles Colborne](http://programmer.97things.oreilly.com/wiki/index.php/Giles_Colborne) \ No newline at end of file diff --git a/zh/thing_67/README.md b/zh/thing_67/README.md new file mode 100644 index 00000000..c9e1e9d8 --- /dev/null +++ b/zh/thing_67/README.md @@ -0,0 +1,19 @@ +# 专业程序员 + +什么是专业程序员? + +专业程序员最重要的特质是*个人责任感*。专业程序员对自己的职业生涯、估算、进度承诺、错误和工作质量负责。专业程序员不会把这种责任推给他人。 + +- 如果你是专业人士,那么*你*要对自己的职业生涯负责。*你*要负责阅读和学习。你要负责跟上行业和技术的步伐。太多程序员认为培训是雇主的责任。抱歉,这是完全错误的。你认为医生会这样吗?你认为律师会这样吗?不,他们利用自己的时间,自费培训自己。他们花很多业余时间阅读期刊和判决书。他们保持自己的知识更新。我们也必须如此。你和雇主之间的关系在雇佣合同中已经明确说明。简而言之:他们承诺支付你薪水,你承诺做好工作。 + +- 专业人士对他们编写的代码负责。他们不会发布不确定是否有效的代码。想一想,如果你愿意发布不确定的代码,你怎么可能认为自己是一个专业人士?专业程序员期望QA(质量保证)*什么都找不到*,因为*他们在彻底测试之前不会发布代码*。当然,QA会发现一些问题,因为没有人是完美的。但作为专业人士,我们的态度必须是:我们不会给QA留下任何问题。 + +- 专业人士是团队合作者。他们对整个团队的产出负责,而不仅仅是自己的工作。他们互相帮助、互相教导、互相学习,甚至在必要时互相掩护。当一个队友倒下时,其他人会介入,因为他们知道有一天他们也会需要掩护。 + +- 专业人士不容忍大量的缺陷列表。一个庞大的缺陷列表是马虎的表现。在问题跟踪数据库中有成千上万问题的系统是粗心大意的悲剧。事实上,在大多数项目中,问题跟踪系统的存在本身就是粗心大意的表现。只有非常大的系统才应该有需要自动化管理的缺陷列表。 + +- 专业人士不会制造混乱。他们为自己的工作质量感到自豪。他们保持代码的整洁、结构良好且易于阅读。他们遵循商定的标准和最佳实践。他们从不匆忙。想象一下,你正在经历一次灵魂出窍的体验,看着一位医生在*你*身上进行心脏手术。这位医生有一个*截止时间*(字面意义上的)。他必须在心肺机损坏你太多血细胞之前完成手术。你希望他如何表现?你希望他像典型的软件开发人员一样匆忙并制造混乱吗?你希望他说:“我稍后会回来修复这个问题”吗?还是你希望他严格遵守他的原则,从容不迫,自信他的方法是他在合理范围内能采取的最佳方法。你想要混乱,还是专业? + +专业人士是有责任感的。他们对自己的职业生涯负责。他们确保自己的代码正常工作。他们对自己的工作质量负责。当截止日期临近时,他们不会放弃自己的原则。事实上,当压力增加时,专业人士会更加坚定地坚持他们认为正确的原则。 + +作者:[Uncle Bob](http://programmer.97things.oreilly.com/wiki/index.php/Uncle_Bob) \ No newline at end of file diff --git a/zh/thing_68/README.md b/zh/thing_68/README.md new file mode 100644 index 00000000..91a507e9 --- /dev/null +++ b/zh/thing_68/README.md @@ -0,0 +1,19 @@ +# 将所有内容置于版本控制之下 + +将你所有项目中的一切都置于版本控制之下。你所需的资源都已具备:免费的工具,如Subversion、Git、Mercurial和CVS;充足的磁盘空间;廉价且强大的服务器;无处不在的网络;甚至还有项目托管服务。安装好版本控制软件后,你只需在包含代码的干净目录中执行适当的命令,就可以将你的工作放入其仓库中。你只需要学习两个新的基本操作:将代码更改*提交*到仓库,并使用仓库的版本更新你的工作版本。 + +一旦你的项目处于版本控制之下,你显然可以跟踪其历史,查看谁编写了什么代码,并通过唯一标识符引用文件或项目版本。更重要的是,你可以大胆地进行代码更改而无需担心——不再需要为了以防将来需要而注释掉代码,因为旧版本安全地保存在仓库中。你可以(也应该)用符号名称标记软件发布,以便将来轻松回顾客户运行的软件的确切版本。你可以创建并行开发的分支:大多数项目都有一个活跃的开发分支和一个或多个用于积极支持的发布版本的维护分支。 + +版本控制系统最大限度地减少了开发者之间的摩擦。当程序员在独立的软件部分上工作时,这些部分几乎会自动集成。当他们互相干扰时,系统会注意到并允许他们解决冲突。通过一些额外的设置,系统可以通知所有开发者每次提交的更改,建立对项目进展的共同理解。 + +当你设置项目时,不要吝啬:将*所有*项目资产置于版本控制之下。除了源代码,还包括文档、工具、构建脚本、测试用例、艺术作品,甚至库。将整个项目安全地放入(定期备份的)仓库中,可以最大限度地减少丢失磁盘或数据的损害。在新机器上设置开发环境只需从仓库中检出项目即可。这简化了在不同平台上分发、构建和测试代码的过程:在每台机器上,一个更新命令就能确保软件是最新版本。 + +一旦你体验到了使用版本控制系统的美妙之处,遵循几条规则将使你和你的团队更加高效: + +- 在单独的操作中提交每个逻辑更改。将许多更改集中在一个提交中会使将来难以分离它们。这在你在项目范围内进行重构或样式更改时尤为重要,这些更改很容易掩盖其他修改。 +- 每次提交时附带一条解释性消息。至少简要描述你所做的更改,但如果你还想记录更改的理由,这是存储它的最佳位置。 +- 最后,避免提交会破坏项目构建的代码,否则你会不受项目其他开发者的欢迎。 + +在版本控制系统下的生活太美好,不要因为容易避免的失误而毁了它。 + +作者:[Diomidis Spinellis](http://programmer.97things.oreilly.com/wiki/index.php/Diomidis_Spinellis) \ No newline at end of file diff --git a/zh/thing_69/README.md b/zh/thing_69/README.md new file mode 100644 index 00000000..74604f3e --- /dev/null +++ b/zh/thing_69/README.md @@ -0,0 +1,50 @@ +# 放下鼠标,远离键盘 + +你已经连续几个小时专注于某个棘手的问题,但依然找不到解决方案。于是你起身伸展一下腿脚,或者去自动贩卖机买点东西,结果在回来的路上,答案突然变得显而易见。 + +这种场景听起来熟悉吗?你是否曾想过为什么会这样?关键在于,当你编程时,大脑的逻辑部分处于活跃状态,而创造性的一面则被压制。只有当你让逻辑部分休息时,创造性的一面才能发挥作用。 + +这里有一个真实的例子:我在清理一些遗留代码时,遇到了一个“有趣”的方法。它的设计目的是验证一个字符串是否包含有效的时间格式 *hh:mm:ss xx*,其中 *hh* 表示小时,*mm* 表示分钟,*ss* 表示秒,*xx* 是 *AM* 或 *PM*。 + +该方法使用以下代码将两个字符(表示小时)转换为数字,并验证其是否在正确的范围内: + +``` +try { + Integer.parseInt(time.substring(0, 2)); +} catch (Exception x) { + return false; +} + +if (Integer.parseInt(time.substring(0, 2)) > 12) { + return false; +} +``` + +同样的代码又出现了两次,只是字符偏移量和上限值有所变化,用于测试分钟和秒。方法的最后几行代码用于检查是否为 AM 或 PM: + +``` +if (!time.substring(9, 11).equals("AM") & + !time.substring(9, 11).equals("PM")) { + return false; +} +``` + +如果这一系列比较都没有失败并返回 false,那么方法将返回 true。 + +如果前面的代码看起来冗长且难以理解,别担心。我也这么认为——这意味着我找到了值得清理的东西。我对其进行了重构,并编写了一些单元测试,以确保它仍然有效。 + +当我完成后,我对结果感到满意。新版本易于阅读,代码量减少了一半,而且更加准确,因为原始代码只测试了小时、分钟和秒的上限。 + +第二天准备上班时,我脑海中突然冒出一个想法:为什么不使用正则表达式来验证字符串呢?经过几分钟的敲打,我得到了一个仅有一行代码的工作实现。代码如下: + +``` +public static boolean validateTime(String time) { + return time.matches("(0[1-9]|1[0-2]):[0-5][0-9]:[0-5][0-9] ([AP]M)"); +} +``` + +这个故事的重点并不是我最终用一行代码替换了三十多行代码。重点在于,直到我离开电脑,我才意识到我的第一次尝试并不是问题的最佳解决方案。 + +所以,下次当你遇到一个棘手的问题时,不妨帮自己一个忙。一旦你真正理解了问题,去做一些涉及大脑创造性方面的事情——画出问题的草图,听一些音乐,或者只是出去散散步。有时候,解决问题的最好方法就是放下鼠标,远离键盘。 + +作者:[BurkHufnagel](http://programmer.97things.oreilly.com/wiki/index.php/BurkHufnagel) \ No newline at end of file diff --git a/zh/thing_70/README.md b/zh/thing_70/README.md new file mode 100644 index 00000000..46a9dc75 --- /dev/null +++ b/zh/thing_70/README.md @@ -0,0 +1,13 @@ +# 阅读代码 + +我们程序员是一群奇怪的生物。我们喜欢编写代码。但当涉及到阅读代码时,我们通常会退缩。毕竟,编写代码要有趣得多,而阅读代码则很困难——有时几乎是不可能的。阅读别人的代码尤其困难。这并不一定是因为别人的代码写得不好,而是因为他们可能以与你不同的方式思考问题和解决问题。但你是否曾想过,阅读别人的代码可能会提高你自己的水平? + +下次你阅读一些代码时,停下来思考一下。这段代码是容易阅读还是难以阅读?如果难以阅读,原因是什么?是格式不好吗?是命名不一致或不合理吗?还是多个关注点混杂在同一段代码中?也许是语言的选择使得代码难以阅读?试着从别人的错误中学习,这样你的代码就不会犯同样的错误。你可能会得到一些惊喜。例如,解耦技术可能对低耦合有好处,但有时它们也会使代码更难阅读。有些人称之为*优雅的代码*,而另一些人则称之为*难以阅读的代码*。 + +如果代码易于阅读,停下来看看是否有你可以从中学习的有用之处。也许其中使用了一个你不知道的设计模式,或者你之前曾难以实现的设计模式。也许方法比你写的更短,命名更具表达力。一些开源项目中充满了如何编写出色、可读代码的好例子——而另一些则恰恰相反!查看一些他们的代码并仔细看看。 + +阅读你自己以前的项目中的旧代码,也可能是一种启发性的体验。从你最早的代码开始,逐步向前推进到现在。你可能会发现,它并不像你编写时那样容易阅读。你早期的代码可能还具有一定的尴尬娱乐价值,有点像提醒你昨晚在酒吧喝酒时说的那些话。看看你多年来是如何提升技能的——这真的可以激励你。观察哪些部分的代码难以阅读,并考虑你今天是否仍然以同样的方式编写代码。 + +所以下次你觉得需要提高编程技能时,不要再去读另一本书了。去阅读代码吧。 + +作者:[Karianne Berg](http://programmer.97things.oreilly.com/wiki/index.php/Karianne_Berg) \ No newline at end of file diff --git a/zh/thing_71/README.md b/zh/thing_71/README.md new file mode 100644 index 00000000..8c6b07ec --- /dev/null +++ b/zh/thing_71/README.md @@ -0,0 +1,13 @@ +# 阅读人文学科 + +在几乎所有开发项目中,人们都是与人合作。在几乎所有研究领域中,人们编写软件都是为了支持他人实现某些目标。人们与人一起编写软件,为人编写软件。这是一个与人打交道的行业。不幸的是,程序员所接受的教育往往使他们非常不擅长处理与他们一起工作的人。幸运的是,有一个完整的学科领域可以帮助他们。 + +例如,路德维希·维特根斯坦在《哲学研究》(以及其他著作)中提出了一个非常有说服力的观点,即我们用来交流的任何语言都不是、也不能是将思想、观念或图像从一个人的头脑中传递到另一个人头脑中的序列化格式。在“收集需求”时,我们应该警惕误解的发生。维特根斯坦还指出,我们彼此理解的能力并不源于共享的定义,而是源于共享的经验和生活方式。这可能就是为什么那些深入问题领域的程序员往往比那些与之疏远的程序员表现更好的原因之一。 + +拉科夫和约翰逊在《我们赖以生存的隐喻》中为我们提供了一系列隐喻的目录,表明语言在很大程度上是隐喻性的,这些隐喻为我们提供了理解世界的方式。即使是像现金流这样看似具体的术语,在讨论金融系统时也可以被视为隐喻:“金钱是一种流体。”这种隐喻如何影响我们处理金钱系统的思维方式?或者我们可能会谈论协议栈中的层次,有些是高层,有些是低层。这是强有力的隐喻:用户是“上”,技术是“下”。这揭示了我们构建系统结构的思维方式。它也可能标志着一种懒惰的思维习惯,我们可能会从中受益,偶尔打破这种习惯。 + +马丁·海德格尔深入研究了人们使用工具的方式。程序员构建和使用工具,我们思考、创造、修改和重建工具。工具是我们感兴趣的对象。但对于用户来说,正如海德格尔在《存在与时间》中所展示的,工具在使用中变得不可见,只有在使用时才能被理解。对于用户来说,工具只有在不工作时才会成为关注的对象。在讨论可用性时,这种重点差异值得牢记。 + +埃莉诺·罗施颠覆了亚里士多德的分类模型,我们通过这些分类来组织对世界的理解。当程序员询问用户对系统的期望时,我们倾向于要求用户提供基于谓词的定义。这对我们来说非常方便。谓词中的术语可以很容易地成为类的属性或表中的列。这些类型的分类是清晰、不重叠且整洁的。不幸的是,正如罗施在《自然类别》及其后续著作中所展示的,这并不是人们通常理解世界的方式。他们通过基于例子的方式理解世界。一些例子,即所谓的原型,比其他例子更好,因此产生的类别是模糊的,它们重叠,可以有丰富的内部结构。只要我们坚持亚里士多德的答案,我们就无法向用户提出关于他们世界的正确问题,并且将难以达成我们所需的共同理解。 + +作者:[Keith Braithwaite](http://programmer.97things.oreilly.com/wiki/index.php/Keith_Braithwaite) \ No newline at end of file diff --git a/zh/thing_72/README.md b/zh/thing_72/README.md new file mode 100644 index 00000000..342e3847 --- /dev/null +++ b/zh/thing_72/README.md @@ -0,0 +1,15 @@ +# 经常重新发明轮子 + +“就用现有的东西吧——重新发明轮子太傻了……” + +你听过这种话或者类似的说法吗?当然听过!每个开发者和学生可能都经常听到这样的评论。但为什么呢?为什么重新发明轮子如此不受待见?因为大多数情况下,现有的代码是经过验证的。它已经通过了某种质量控制、严格的测试,并且正在成功使用。此外,投入在重新发明上的时间和精力不太可能比使用现有产品或代码库带来更好的回报。你应该费心去重新发明轮子吗?为什么?什么时候? + +也许你看过关于软件开发模式的文章,或者软件设计的书籍。无论这些书中的信息多么精彩,它们可能都让人昏昏欲睡。就像看一部关于航海的电影和真正去航海是完全不同的体验一样,使用现有代码与从头开始设计自己的软件、测试它、破坏它、修复它并在此过程中改进它也是截然不同的。 + +重新发明轮子不仅仅是一个关于如何放置代码结构的练习:它是如何深入了解各种现有组件内部工作原理的过程。你知道内存管理器是如何工作的吗?虚拟分页呢?你能自己实现这些吗?双向链表呢?动态数组类呢?ODBC客户端呢?你能写出一个像你熟悉并喜欢的流行图形用户界面那样的界面吗?你能创建自己的网页浏览器小部件吗?你知道什么时候该写一个多路复用系统而不是多线程系统吗?如何在基于文件或基于内存的数据库之间做出选择?大多数开发者从未亲自创建过这些类型的核心软件实现,因此并不深入了解它们的工作原理。结果是,所有这些软件都被视为神秘的黑盒子,只是“能用”。只了解表面是不够的,无法揭示隐藏在水下的危险。不了解软件开发中的深层知识会限制你创造出色作品的能力。 + +重新发明轮子并犯错比第一次就做对更有价值。从试错中学到的经验教训带有情感成分,这是仅仅阅读技术书籍无法提供的! + +学到的知识和书本上的智慧固然重要,但成为一名伟大的程序员不仅在于积累知识,还在于获取经验。重新发明轮子对开发者的教育和技能的重要性,就像举重对健美运动员的重要性一样。 + +作者:[Jason P Sage](http://programmer.97things.oreilly.com/wiki/index.php/Jason_P_Sage) \ No newline at end of file diff --git a/zh/thing_73/README.md b/zh/thing_73/README.md new file mode 100644 index 00000000..9f163e89 --- /dev/null +++ b/zh/thing_73/README.md @@ -0,0 +1,25 @@ +# 抵制单例模式的诱惑 + +单例模式解决了许多问题。你知道你只需要一个实例。你保证这个实例在使用前已被初始化。它通过提供一个全局访问点来保持设计的简洁。这一切都很好。这个经典的设计模式有什么不讨人喜欢的地方呢? + +事实上,问题很多。尽管单例模式可能很诱人,但经验表明,大多数单例模式实际上弊大于利。它们阻碍了可测试性,损害了可维护性。不幸的是,这种额外的智慧并没有像它应该的那样广泛传播,单例模式对许多程序员来说仍然具有不可抗拒的吸引力。但值得抵制: + +- 单实例的需求往往是想象出来的。在许多情况下,未来不需要额外实例的假设纯粹是推测。在应用程序设计中传播这种推测性的属性,必然会在某个时候带来痛苦。需求会发生变化。好的设计会拥抱这种变化。单例模式则不会。 + +- 单例模式在概念上独立的代码单元之间引入了隐式依赖关系。这之所以有问题,是因为它们既隐藏了依赖关系,又在单元之间引入了不必要的耦合。当你尝试编写单元测试时,这种代码异味会变得尤为明显,因为单元测试依赖于松耦合以及能够选择性地用模拟实现替换真实实现。单例模式阻止了这种直接的模拟。 + +- 单例模式还带有隐式的持久状态,这同样阻碍了单元测试。单元测试依赖于测试之间的独立性,因此测试可以以任何顺序运行,并且在每个单元测试执行之前,程序可以设置为已知状态。一旦你引入了带有可变状态的单例模式,这可能就很难实现。此外,这种全局可访问的持久状态使得代码更难理解,尤其是在多线程环境中。 + +- 多线程进一步加剧了单例模式的陷阱。由于在访问时直接锁定效率不高,所谓的双重检查锁定模式(DCLP)变得流行起来。不幸的是,这可能是一种更具致命吸引力的形式。事实证明,在许多语言中,DCLP 并不是线程安全的,即使在某些语言中是线程安全的,仍然有可能在细微之处出错。 + +单例模式的清理可能会带来最后的挑战: + +- 没有明确支持销毁单例模式,这在某些情况下可能是一个严重的问题。例如,在插件架构中,只有在所有对象都被清理后,插件才能安全卸载。 + +- 在程序退出时,单例模式的隐式清理没有顺序。这对于包含相互依赖的单例模式的应用程序来说可能会很麻烦。在关闭此类应用程序时,一个单例模式可能会访问另一个已经被销毁的单例模式。 + +- 通过引入额外的机制可以克服其中一些缺点。然而,这会以增加代码复杂性为代价,而这种复杂性本可以通过选择替代设计来避免。 + +因此,请将单例模式的使用限制在那些真正只能实例化一次的类上。不要从任意代码中使用单例模式的全局访问点。相反,对单例模式的直接访问应该仅限于少数明确定义的地方,从这些地方可以通过其接口将单例模式传递给其他代码。这些其他代码并不知道单例模式的存在,因此也不依赖于单例模式或任何其他类是否实现了该接口。这打破了阻碍单元测试的依赖关系,并提高了可维护性。所以,下次你考虑实现或访问单例模式时,希望你能够停下来,再想一想。 + +作者:[Sam Saariste](http://programmer.97things.oreilly.com/wiki/index.php/Sam_Saariste) \ No newline at end of file diff --git a/zh/thing_74/README.md b/zh/thing_74/README.md new file mode 100644 index 00000000..5771cdef --- /dev/null +++ b/zh/thing_74/README.md @@ -0,0 +1,13 @@ +# 性能优化之路布满“脏代码炸弹” + +通常情况下,优化系统性能需要修改代码。当我们需要修改代码时,每一块过于复杂或高度耦合的代码都是一个潜在的“脏代码炸弹”,随时可能破坏我们的优化工作。脏代码的第一个受害者将是你的时间表。如果前进的道路是平坦的,那么预测完成时间将很容易。然而,意外遇到脏代码会使预测变得非常困难。 + +考虑这样一种情况:你发现了一个执行热点。通常的做法是降低底层算法的复杂度。假设你向经理估计需要3-4小时来完成这个修复。当你开始应用这个修复时,你很快意识到你破坏了一个依赖部分。由于紧密相关的事物通常必然耦合,这种破坏很可能是预料之中的,并且已经考虑在内。但如果修复这个依赖导致其他依赖部分也出现问题呢?此外,依赖关系离源头越远,你越不可能意识到它,并在你的估计中考虑它。突然间,你的3-4小时估计很容易膨胀到3-4周。通常,这种时间表的意外膨胀每次会持续1到2天。常见的“快速”重构最终可能需要几个月才能完成。在这些情况下,负责团队的信誉和政治资本将受到严重甚至致命的损害。如果我们有一个工具来帮助我们识别和衡量这种风险就好了。 + +事实上,我们有很多方法来衡量和控制代码的耦合度和复杂度。软件度量可以用来计算代码中特定特征的出现次数。这些计数值与代码质量相关。衡量耦合度的两个度量指标是扇入和扇出。以类的扇出为例:它被定义为从感兴趣的类直接或间接引用的类的数量。你可以将其视为在编译你的类之前必须编译的所有类的数量。另一方面,扇入是所有依赖于感兴趣类的类的数量。知道扇出和扇入后,我们可以使用公式 *I = fo / (fi + fo)* 来计算一个不稳定因子。当 *I* 接近0时,包变得更加稳定。当 *I* 接近1时,包变得不稳定。稳定的包是低风险的重新编码目标,而不稳定的包更可能充满脏代码炸弹。重构的目标是将 *I* 移近0。 + +在使用度量时,必须记住它们只是经验法则。从数学上讲,我们可以看到在不改变 *fo* 的情况下增加 *fi* 会使 *I* 接近0。然而,非常大的扇入值也有一个缺点,即这些类在不破坏依赖项的情况下更难修改。此外,如果不解决扇出问题,你并没有真正降低风险,因此必须应用一些平衡。 + +软件度量的一个缺点是,度量工具产生的大量数字可能会让不熟悉的人感到畏惧。尽管如此,软件度量可以成为我们在追求干净代码的过程中一个强大的工具。它们可以帮助我们在脏代码炸弹对性能优化工作构成严重风险之前识别并消除它们。 + +作者:[Kirk Pepperdine](http://programmer.97things.oreilly.com/wiki/index.php/Kirk_Pepperdine) \ No newline at end of file diff --git a/zh/thing_75/README.md b/zh/thing_75/README.md new file mode 100644 index 00000000..c16a0766 --- /dev/null +++ b/zh/thing_75/README.md @@ -0,0 +1,19 @@ +# 简单源于删减 + +“重来一遍……”我的老板一边对我说,一边用力按下删除键。我盯着电脑屏幕,一种再熟悉不过的失落感涌上心头,我的代码——一行接一行——消失得无影无踪。 + +我的老板Stefan并不是一个话多的人,但他一眼就能看出糟糕的代码。而且他非常清楚该如何处理这些代码。 + +我以学生程序员的身份进入现在的岗位时,充满了精力和热情,但对如何编写代码一无所知。我有一个糟糕的习惯,认为解决每个问题的方法就是在某个地方添加另一个变量,或者再写一行代码。在糟糕的日子里,我的代码并没有随着每次修改而变得逻辑更清晰,反而逐渐变得更大、更复杂,离稳定运行越来越远。 + +这是很自然的,尤其是在匆忙的时候,人们往往只想对现有的代码块做最少的修改,即使这些代码很糟糕。大多数程序员会保留糟糕的代码,担心从头开始会比回到起点需要更多的努力。对于接近可运行的代码来说,这可能是真的,但有些代码已经无可救药了。 + +试图挽救糟糕的代码往往会浪费比预期更多的时间。一旦某样东西变成了资源的无底洞,就需要迅速丢弃它。 + +这并不是说我们应该轻易地丢弃所有的打字、命名和格式化工作。我老板的反应虽然极端,但它确实迫使我重新思考代码,在第二次(或偶尔第三次)尝试时做出改进。然而,修复糟糕代码的最佳方法是进入一种模式,无情地重构、调整或删除代码。 + +代码应该是简单的。变量、函数、声明和其他语法要素的数量应该尽可能少。多余的行、多余的变量……任何多余的东西,都应该被清除。立即删除。剩下的代码应该只够完成任务,完成算法或执行计算。其他任何东西都是多余的噪音,它们无意中被引入,模糊了代码的流程,掩盖了重要的部分。 + +当然,如果这样还不行,那就干脆全部删除,重新输入一遍。通过这种方式从记忆中提取代码,往往能帮助清除大量不必要的混乱。 + +作者:[Paul W. Homer](http://programmer.97things.oreilly.com/wiki/index.php/Paul_W._Homer) \ No newline at end of file diff --git a/zh/thing_76/README.md b/zh/thing_76/README.md new file mode 100644 index 00000000..99c4aa25 --- /dev/null +++ b/zh/thing_76/README.md @@ -0,0 +1,41 @@ +# 单一职责原则 + +良好设计的最基本原则之一是: + +> 将因相同原因而变化的事物聚集在一起,将因不同原因而变化的事物分开。 + +这一原则通常被称为*单一职责原则*(Single Responsibility Principle,简称SRP)。简而言之,它指出一个子系统、模块、类,甚至一个函数,都不应该有超过一个改变的理由。经典的例子是一个类包含了处理业务规则、报告和数据库的方法: + +```java +public class Employee { + public Money calculatePay() ... + public String reportHours() ... + public void save() ... +} +``` + +一些程序员可能会认为将这三个函数放在同一个类中是合适的。毕竟,类应该是操作共同变量的函数集合。然而,问题在于这三个函数因完全不同的原因而变化。`calculatePay` 函数会在计算薪酬的业务规则发生变化时改变。`reportHours` 函数会在有人想要不同的报告格式时改变。`save` 函数会在数据库管理员更改数据库模式时改变。这三个变化的原因结合在一起,使得 `Employee` 类非常不稳定。它会因其中任何一个原因而改变。更重要的是,任何依赖于 `Employee` 的类都会受到这些变化的影响。 + +良好的系统设计意味着我们将系统分离为可以独立部署的组件。独立部署意味着如果我们更改一个组件,我们不需要重新部署其他任何组件。然而,如果 `Employee` 被其他组件中的许多类广泛使用,那么每次对 `Employee` 的更改都可能导致其他组件需要重新部署;从而否定了组件设计(或如果你更喜欢时髦的名称,SOA)的主要优势。 + +```java +public class Employee { + public Money calculatePay() ... +} + +public class EmployeeReporter { + public String reportHours(Employee e) ... +} + +public class EmployeeRepository { + public void save(Employee e) ... +} +``` + +上面展示的简单划分解决了这些问题。每个类都可以放在自己的组件中。或者更准确地说,所有与报告相关的类可以放在报告组件中。所有与数据库相关的类可以放在存储库组件中。所有业务规则可以放在业务规则组件中。 + +敏锐的读者会发现,上述解决方案中仍然存在依赖关系。`Employee` 仍然被其他类依赖。因此,如果 `Employee` 被修改,其他类可能也需要重新编译和重新部署。因此,`Employee` 不能被修改后独立部署。然而,其他类可以被修改并独立部署。对其中一个类的修改不会强制其他类重新编译或重新部署。甚至 `Employee` 也可以通过谨慎使用*依赖倒置原则*(Dependency Inversion Principle,DIP)来独立部署,但这是[另一本书](http://www.amazon.com/dp/0135974445/)的主题。 + +谨慎应用 SRP,将因不同原因而变化的事物分开,是创建具有独立可部署组件结构的设计的关键之一。 + +作者:[Uncle Bob](http://programmer.97things.oreilly.com/wiki/index.php/Uncle_Bob) \ No newline at end of file diff --git a/zh/thing_77/README.md b/zh/thing_77/README.md new file mode 100644 index 00000000..eaed3218 --- /dev/null +++ b/zh/thing_77/README.md @@ -0,0 +1,23 @@ +# 从“是”开始 + +最近我在一家杂货店里四处寻找“毛豆”(我只模糊地知道这是一种蔬菜)。我不确定这是在蔬菜区、冷冻区还是罐装区能找到的东西。我放弃了,找到一位员工寻求帮助。她也不知道! + +这位员工本可以用许多不同的方式回应。她可以让我因为不知道去哪里找而感到无知,或者给我一些模糊的可能性,甚至直接告诉我他们没有这个商品。但她却把这个请求当作一个找到解决方案并帮助顾客的机会。她叫来了其他员工,几分钟内就引导我找到了确切的东西,它就藏在冷冻区。 + +在这个例子中,这位员工看待请求时,从“我们会解决问题并满足请求”这一前提出发。她是从“是”开始的,而不是从“不”开始。 + +当我第一次被安排到一个技术领导职位时,我觉得我的工作是保护我漂亮的软件免受产品经理和业务分析师们荒谬需求的冲击。我在大多数对话中把请求看作是需要击败的东西,而不是需要满足的东西。 + +在某个时刻,我突然意识到,也许有一种不同的工作方式,只需要将我的视角从“不”转变为“是”。事实上,我开始相信,从“是”开始实际上是成为技术领导者的一个重要部分。 + +这个简单的改变彻底改变了我对待工作的方式。事实证明,有很多方式可以说“是”。当有人对你说“嘿,如果我们把所有的窗口都做成圆形和半透明的,这个应用会非常棒!”你可以认为这是荒谬的并拒绝。但通常更好的做法是从“为什么?”开始。通常,这个人之所以要求圆形半透明的窗口,是有一些实际且令人信服的原因的。例如,你可能即将签下一个新的大客户,而这个客户的标准委员会要求圆形半透明的窗口。 + +通常你会发现,当你了解请求的背景时,新的可能性就会打开。通常可以通过现有产品的其他方式满足请求,让你完全不需要额外工作就能说“是”:“实际上,在用户偏好设置中,你可以下载圆形半透明的窗口皮肤并启用它。” + +有时候,对方只是有一个你认为与产品理念不符的想法。我发现通常对自己提出“为什么?”是有帮助的。有时候,说出原因会让你清楚你的第一反应并不合理。如果没有,你可能需要提高一个层次,引入其他关键决策者。记住,这一切的目标是对对方说“是”,并努力让它实现,不仅是为了他,也是为了你和你的团队。 + +如果你能说出一个令人信服的解释,说明为什么这个功能请求与现有产品不兼容,那么你很可能会就“我们是否在构建正确的产品”展开一次富有成效的对话。无论对话如何结束,每个人都会更清晰地聚焦于产品是什么,以及它不是什么。 + +从“是”开始意味着与同事合作,而不是对抗他们。 + +作者:[Alex Miller](http://programmer.97things.oreilly.com/wiki/index.php/Alex_Miller) \ No newline at end of file diff --git a/zh/thing_78/README.md b/zh/thing_78/README.md new file mode 100644 index 00000000..ce71eddc --- /dev/null +++ b/zh/thing_78/README.md @@ -0,0 +1,29 @@ +# 退一步,自动化,自动化,再自动化 + +我曾与一些程序员共事,当被要求统计某个模块的代码行数时,他们会将文件粘贴到文字处理器中,并使用其“行数统计”功能。而且他们下周还会这样做。再下周也是如此。这很糟糕。 + +我曾参与一个项目,其部署过程非常繁琐,涉及代码签名并将结果移动到服务器,需要多次点击鼠标。有人将其自动化了,结果在最终测试期间,脚本运行了数百次,远超预期。这很好。 + +那么,为什么人们会一遍又一遍地重复同样的任务,而不是退一步花时间将其自动化呢? + +## 常见误解 #1:自动化只适用于测试。 + +当然,测试自动化很棒,但为什么要止步于此呢?在任何项目中,重复性任务比比皆是:版本控制、编译、构建 JAR 文件、文档生成、部署和报告。对于许多这些任务,脚本比鼠标更强大。执行繁琐的任务变得更快、更可靠。 + +## 常见误解 #2:我有 IDE,所以我不需要自动化。 + +你是否曾与队友有过“但它在我机器上(检查通过|构建成功|测试通过)?”的争论?现代 IDE 有成千上万的潜在设置,几乎不可能确保所有团队成员都有相同的配置。像 Ant 或 Autotools 这样的构建自动化系统为你提供了控制和可重复性。 + +## 常见误解 #3:我需要学习奇特的工具才能自动化。 + +使用一个像样的 shell 语言(如 bash 或 PowerShell)和一个构建自动化系统,你可以走得很远。如果你需要与网站交互,可以使用像 iMacros 或 Selenium 这样的工具。 + +## 常见误解 #4:我无法自动化这个任务,因为我无法处理这些文件格式。 + +如果你的流程中有一部分需要 Word 文档、电子表格或图像,那么自动化它可能确实具有挑战性。但这真的必要吗?你能使用纯文本吗?逗号分隔值?XML?一个从文本文件生成图形的工具?通常,对流程进行微调可以产生良好的结果,同时大大减少繁琐性。 + +## 常见误解 #5:我没有时间弄清楚。 + +你不需要学习所有的 bash 或 Ant 才能开始。边学边做。当你有一个你认为可以且应该自动化的任务时,只需学习足够多的工具知识来完成它。并且在项目初期,当时间通常更容易找到时,就去做。一旦你成功了,你(和你的老板)就会看到投资自动化是有意义的。 + +作者:[Cay Horstmann](http://programmer.97things.oreilly.com/wiki/index.php/Cay_Horstmann) \ No newline at end of file diff --git a/zh/thing_79/README.md b/zh/thing_79/README.md new file mode 100644 index 00000000..7bfcb985 --- /dev/null +++ b/zh/thing_79/README.md @@ -0,0 +1,13 @@ +# 利用代码分析工具 + +测试的价值在软件开发者的编程旅程早期就被反复强调。近年来,随着单元测试、测试驱动开发和敏捷方法的兴起,人们越来越重视在整个开发周期中充分利用测试。然而,测试只是众多可以用来提高代码质量的工具之一。 + +在很久以前,C语言还是一个新兴事物时,CPU时间和任何形式的存储都非常宝贵。早期的C编译器考虑到这一点,因此通过移除一些语义分析来减少对代码的遍历次数。这意味着编译器只检查了一小部分可以在编译时检测到的错误。为了弥补这一点,Stephen Johnson编写了一个名为*lint*的工具——它可以去除代码中的冗余——实现了一些从其姊妹C编译器中移除的静态分析功能。然而,静态分析工具因产生大量误报警告和关于风格规范的警告而声名狼藉,而这些规范并不总是必须遵循的。 + +如今的语言、编译器和静态分析工具的情况已经大不相同。内存和CPU时间现在相对便宜,因此编译器可以承担起检查更多错误的责任。几乎每种语言都至少有一个工具来检查违反风格指南、常见陷阱以及有时难以捕捉的狡猾错误,例如潜在的空指针解引用。更复杂的工具,如用于C语言的Splint或用于Python的Pylint,是可配置的,这意味着你可以通过配置文件、命令行开关或在IDE中选择工具发出的错误和警告。Splint甚至允许你在注释中为代码添加注解,以便更好地提示程序的工作原理。 + +如果所有其他方法都失败了,而你发现自己正在寻找编译器、IDE或lint工具未捕获的简单错误或违反标准的情况,那么你总是可以自己编写一个静态检查器。这并不像听起来那么困难。大多数语言,特别是那些标榜为*动态*的语言,将它们的抽象语法树和编译器工具作为标准库的一部分公开。深入了解你所使用语言的开发团队所使用的标准库的冷门角落是非常值得的,因为这些库通常包含对静态分析和动态测试有用的隐藏宝藏。例如,Python标准库包含一个反汇编器,它可以告诉你用于生成某些编译代码或代码对象的字节码。这听起来像是为python-dev团队的编译器编写者准备的晦涩工具,但实际上在日常情况下非常有用。这个库可以反汇编你最后的堆栈跟踪,为你提供关于究竟是哪个字节码指令抛出了最后一个未捕获异常的反馈。 + +所以,不要让测试成为你质量保证的终点——充分利用分析工具,并且不要害怕自己动手编写工具。 + +作者:[Sarah Mount](http://programmer.97things.oreilly.com/wiki/index.php/Sarah_Mount) \ No newline at end of file diff --git a/zh/thing_80/README.md b/zh/thing_80/README.md new file mode 100644 index 00000000..8fcfd95d --- /dev/null +++ b/zh/thing_80/README.md @@ -0,0 +1,15 @@ +# 测试必要行为,而非偶然行为 + +测试中的一个常见陷阱是假设实现所做的正是你想要测试的内容。乍一听,这似乎更像是一种美德而非陷阱。然而,换一种说法,问题就更加明显了:测试中的一个常见陷阱是将测试硬编码到实现的具体细节中,而这些细节是偶然的,与所需功能无关。 + +当测试与实现的偶然细节硬编码在一起时,对实现所做的实际上与所需行为兼容的更改可能会导致测试失败,从而导致误报。程序员通常的反应是重写测试或重写代码。假设误报实际上是真正的正报,通常是恐惧、不确定性或怀疑的结果。这会将偶然行为提升为必要行为。在重写测试时,程序员要么将测试重新聚焦于所需行为(好的做法),要么简单地将其硬编码到新的实现中(不好的做法)。测试需要足够精确,但也需要准确。 + +例如,在三向比较中,如C语言的`strcmp`或Java的`String.compareTo`,对结果的要求是:如果左侧小于右侧,则结果为负;如果左侧大于右侧,则结果为正;如果它们被视为相等,则结果为零。这种比较风格用于许多API中,包括C语言`qsort`函数的比较器和Java`Comparable`接口中的`compareTo`。尽管在实现中通常使用特定值`-1`和`+1`分别表示*小于*和*大于*,但程序员常常错误地认为这些值代表了实际要求,并因此编写测试来公开这一假设。 + +类似的问题也出现在断言间距、精确措辞以及其他文本格式化和呈现方面的测试中,这些方面都是偶然的。除非你在编写例如提供可配置格式化的XML生成器,否则间距对结果不应有影响。同样,将按钮和标签的位置硬编码到UI控件中会减少未来更改和完善这些偶然细节的选项。实现中的微小更改和格式上的无关紧要的更改突然成为构建破坏者。 + +过度指定的测试通常是单元测试中白盒方法的一个问题。白盒测试使用代码的结构来确定所需的测试用例。白盒测试的典型失败模式是测试最终断言代码做了代码所做的事情。简单地重复代码中已经显而易见的内容并没有增加价值,反而会导致一种虚假的进展感和安全感。 + +为了有效,测试需要陈述契约义务,而不是鹦鹉学舌地重复实现。它们需要对被测单元采取黑盒视角,以可执行的形式勾勒出接口契约。因此,将测试行为与所需行为对齐。 + +作者:[Kevlin Henney](http://programmer.97things.oreilly.com/wiki/index.php/Kevlin_Henney) \ No newline at end of file diff --git a/zh/thing_81/README.md b/zh/thing_81/README.md new file mode 100644 index 00000000..67cd6a7c --- /dev/null +++ b/zh/thing_81/README.md @@ -0,0 +1,45 @@ +# 精确且具体的测试 + +重要的是测试代码单元所需的、本质的行为,而不是测试其特定实现的偶然行为。但这不应被误解为模糊测试的借口。测试需要既准确又精确。 + +排序例程是一个经过尝试、测试和验证的经典例子,可以很好地说明这一点。实现排序算法不一定是程序员的日常任务,但排序是一个如此熟悉的概念,以至于大多数人认为他们知道对它的期望。然而,这种随意的熟悉感可能会让人更难看清某些假设。 + +当程序员被问到“你会测试什么?”时,最常见的回答是“排序的结果是一个有序的元素序列。”虽然这是正确的,但这并不是全部真相。当被要求提供更精确的条件时,许多程序员会补充说,结果序列的长度应与原始序列相同。尽管这是正确的,但这仍然不够。例如,给定以下序列: + +``` +3 1 4 1 5 9 +``` + +以下序列满足排序后的非递减顺序且长度与原始序列相同的后置条件: + +``` +3 3 3 3 3 3 +``` + +尽管它满足了规范,但这显然不是我们想要的结果!这个例子基于一个来自实际生产代码的错误(幸运的是在发布前被捕获),其中由于一个简单的按键错误或一时的疏忽,导致了一个复杂的机制,用给定数组的第一个元素填充了整个结果。 + +完整的后置条件是结果是有序的,并且它包含原始值的一个排列。这适当地约束了所需的行为。结果长度与输入长度相同是自然而然的结果,不需要再重复说明。 + +即使以后置条件的方式描述也不足以提供一个好的测试。一个好的测试应该是可读的。它应该易于理解且足够简单,以便你可以轻松看出它是否正确(或错误)。除非你已经有了检查序列是否有序以及一个序列是否包含另一个序列的值的排列的代码,否则测试代码很可能会比被测试的代码更复杂。正如 Tony Hoare 所说: + +> 有两种构建软件设计的方法:一种方法是让它非常简单,以至于显然没有缺陷;另一种方法是让它非常复杂,以至于没有明显的缺陷。 + +使用具体的例子可以消除这种偶然的复杂性和出错的机会。例如,给定以下序列: + +``` +3 1 4 1 5 9 +``` + +排序的结果是: + +``` +1 1 3 4 5 9 +``` + +没有其他答案可以接受。不接受任何替代品。 + +具体的例子有助于以易于理解且明确的方式说明一般行为。向空集合添加一个项目的结果不仅仅是集合不为空:而是集合现在有一个项目。并且该项目的值是添加的项目。两个或更多项目将符合不为空的条件。但也是错误的。一个不同值的单个项目也是错误的。向表中添加一行的结果不仅仅是表多了一行。它还意味着可以使用该行的键来恢复添加的行。以此类推。 + +在指定行为时,测试不应仅仅是准确的:它们还必须是精确的。 + +作者:[Kevlin Henney](http://programmer.97things.oreilly.com/wiki/index.php/Kevlin_Henney) \ No newline at end of file diff --git a/zh/thing_82/README.md b/zh/thing_82/README.md new file mode 100644 index 00000000..e276f55c --- /dev/null +++ b/zh/thing_82/README.md @@ -0,0 +1,15 @@ +# 在你睡觉时(以及周末)进行测试 + +放松。我并不是在谈论离岸开发中心、周末加班或夜班工作。相反,我想让你注意到我们手头有多少计算能力。具体来说,我们有多少计算能力没有被利用来让程序员的生活变得更轻松一些。你是否经常发现工作日难以获得足够的计算能力?如果是这样,你的测试服务器在正常工作之外的时间在做什么?大多数情况下,测试服务器在夜间和周末是闲置的。你可以利用这一点。 + +- *你是否曾经在没有运行所有测试的情况下提交了更改?* 程序员在提交代码之前不运行测试套件的主要原因之一是它们可能需要很长时间。当截止日期临近,压力山大时,人们自然会开始偷工减料。解决这个问题的一种方法是将大型测试套件分解为两个或多个配置文件。一个较小且强制性的测试配置文件,运行速度快,有助于确保在每次提交之前运行测试。所有的测试配置文件(包括强制性配置文件——只是为了确保)都可以自动化在夜间运行,准备在早上报告结果。 + +- *你有足够的机会测试产品的稳定性吗?* 长时间运行的测试对于识别内存泄漏和其他稳定性问题至关重要。它们很少在白天运行,因为这会占用时间和资源。你可以自动化一个浸泡测试在夜间运行,并在周末运行更长时间。从周五下午6点到下周一早上6点,有60小时的潜在测试时间。 + +- *你在性能测试环境中获得了足够的高质量时间吗?* 我见过团队为了在性能测试环境中获得时间而争吵。在大多数情况下,两个团队在白天都没有获得足够的高质量时间,而环境在下班后几乎处于闲置状态。服务器和网络在夜间或周末并不那么繁忙。这是运行一些高质量性能测试的理想时间。 + +- *手动测试的排列组合是否太多?* 在许多情况下,你的产品旨在在各种平台上运行。例如,32位和64位,在Linux、Solaris和Windows上,或者仅仅是在同一操作系统的不同版本上。更糟糕的是,许多现代应用程序暴露在大量的传输机制和协议(HTTP、AMQP、SOAP、CORBA等)中。手动测试所有这些排列组合非常耗时,并且由于资源压力,很可能在接近发布时完成。唉,可能在这个周期中为时已晚,无法捕捉到某些讨厌的错误。 + +在夜间或周末运行的自动化测试将确保所有这些排列组合更频繁地被测试。通过一些思考和脚本知识,你可以安排一些*cron*作业在夜间和周末启动一些测试。还有许多测试工具可以帮助你。一些组织甚至拥有跨不同部门和团队的服务器网格,以确保资源得到有效利用。如果你的组织中有这种资源,你可以提交测试在夜间或周末运行。 + +作者:[Rajith Attapattu](http://programmer.97things.oreilly.com/wiki/index.php/Rajith_Attapattu) \ No newline at end of file diff --git a/zh/thing_83/README.md b/zh/thing_83/README.md new file mode 100644 index 00000000..a3689d37 --- /dev/null +++ b/zh/thing_83/README.md @@ -0,0 +1,11 @@ +# 测试是软件开发的工程严谨性 + +开发人员在试图向家人、配偶和其他非技术人员解释他们的工作时,喜欢使用夸张的比喻。我们经常用桥梁建设和其他“硬”工程学科来打比方。然而,当你开始过度推敲这些比喻时,它们很快就会站不住脚。事实证明,软件开发在许多重要方面与许多“硬”工程学科并不相似。 + +与“硬”工程相比,软件开发世界大约处于桥梁建设者们在常见策略是建造一座桥,然后让重物从上面滚过的阶段。如果桥没有倒塌,那就是一座好桥。如果倒塌了,那就得重新设计。在过去的几千年里,工程师们发展了数学和物理学,可以在不实际建造的情况下使用这些知识来设计结构解决方案。我们在软件领域没有类似的东西,也许永远也不会有,因为软件实际上非常不同。关于软件“工程”与常规工程的深入比较,Jack Reeves 在1992年写的《什么是软件设计?》是一篇经典文章。尽管这篇文章写于近二十年前,但它仍然非常准确。他在这个比较中描绘了一幅黯淡的图景,但1992年所缺少的是对软件的强烈测试文化。 + +测试“硬”东西很困难,因为你必须建造它们才能测试它们,这阻碍了仅仅为了看看会发生什么而进行的推测性建造。但软件中的建造过程却便宜得离谱。我们已经开发了一整套工具生态系统,使得测试变得非常容易:单元测试、模拟对象、测试框架等等。其他工程师会喜欢能够建造一些东西并在现实条件下进行测试。作为软件开发人员,我们应该将测试作为软件的主要(但不是唯一的)验证机制。与其等待某种软件微积分的出现,我们已经拥有了确保良好工程实践的工具。从这个角度来看,我们现在有了反驳那些告诉我们“我们没有时间测试”的经理的武器。桥梁建设者永远不会从他们的老板那里听到“不要费心对那座建筑进行结构分析——我们的时间很紧”。认识到测试确实是软件可重复性和质量的途径,使我们作为开发人员能够反驳那些反对测试的论点,认为这是职业上的不负责任。 + +测试需要时间,就像结构分析需要时间一样。这两项活动都确保了最终产品的质量。现在是软件开发人员为他们所生产的产品承担责任的时候了。仅仅测试是不够的,但它是必要的。测试*就是*软件开发的工程严谨性。 + +作者:[Neal Ford](http://programmer.97things.oreilly.com/wiki/index.php/Neal_Ford) \ No newline at end of file diff --git a/zh/thing_84/README.md b/zh/thing_84/README.md new file mode 100644 index 00000000..772e0be1 --- /dev/null +++ b/zh/thing_84/README.md @@ -0,0 +1,39 @@ +# 状态思维 + +现实世界中的人们与状态有着一种奇怪的关系。今天早上,我路过当地的一家商店,准备为将咖啡因转化为代码的又一天做准备。由于我最喜欢的方式是喝拿铁咖啡,而我找不到任何牛奶,于是我问了店员。 + +“抱歉,我们的牛奶超级、超级缺货。” + +对于程序员来说,这是一个奇怪的陈述。你要么有牛奶,要么没有。在缺货的情况下,没有程度之分。也许她试图告诉我他们将缺货一周,但结果是一样的——对我来说是浓缩咖啡日。 + +在大多数现实世界的情况下,人们对状态的放松态度并不是问题。然而,不幸的是,许多程序员对状态也非常模糊——这是一个问题。 + +考虑一个只接受信用卡且不为客户开具发票的简单网店,其中有一个包含以下方法的 `Order` 类: + +``` + public boolean isComplete() { + return isPaid() && hasShipped(); + } +``` + +合理,对吧?好吧,即使这个表达式被很好地提取到一个方法中,而不是到处复制粘贴,这个表达式也不应该存在。它的存在突显了一个问题。为什么?因为订单在付款之前不能发货。因此,除非 `isPaid` 为真,否则 `hasShipped` 不能为真,这使得表达式的一部分变得多余。你可能仍然希望 `isComplete` 在代码中更清晰,但它应该看起来像这样: + +``` + public boolean isComplete() { + return hasShipped(); + } +``` + +在我的工作中,我经常看到遗漏的检查和冗余的检查。这个例子很小,但当你添加取消和退款时,它会变得更加复杂,良好的状态处理需求也会增加。在这种情况下,订单只能处于三种不同的状态之一: + +- *进行中:* 可以添加或删除商品。不能发货。 +- *已付款:* 不能添加或删除商品。可以发货。 +- *已发货:* 完成。不再接受更改。 + +这些状态很重要,你需要在执行操作之前检查你是否处于预期状态,并且你只能从当前状态移动到合法状态。简而言之,你必须在正确的地方小心地保护你的对象。 + +但你如何开始思考状态呢?将表达式提取为有意义的方法是一个非常好的开始,但这只是一个开始。基础是理解状态机。我知道你可能在计算机科学课上留下了不好的回忆,但把它们抛在脑后。状态机并不特别难。将它们可视化,使它们易于理解和讨论。通过测试驱动你的代码来揭示有效和无效的状态和转换,并保持它们的正确性。研究状态模式。当你感到舒适时,阅读契约设计。它通过在每个公共方法的入口和出口验证传入的数据和对象本身,帮助你确保有效状态。 + +如果你的状态不正确,那就是一个错误,如果你不中止,你就有可能破坏数据。如果你发现状态检查是噪音,学习如何使用工具、代码生成、编织或切面来隐藏它们。无论你选择哪种方法,状态思维都会使你的代码更简单、更健壮。 + +作者:[Niclas Nilsson](http://programmer.97things.oreilly.com/wiki/index.php/Niclas_Nilsson) \ No newline at end of file diff --git a/zh/thing_85/README.md b/zh/thing_85/README.md new file mode 100644 index 00000000..44e259a5 --- /dev/null +++ b/zh/thing_85/README.md @@ -0,0 +1,23 @@ +# 两人智慧胜一人 + +编程需要深思熟虑,而深思熟虑需要独处。这是程序员的一种刻板印象。 + +这种“独狼”式的编程方式正在逐渐让位于一种更加协作的方式,我认为这种方式可以提高程序员的质量、生产力和工作满意度。这种方式使开发人员彼此之间以及与业务和系统分析师、质量保证专业人员和用户等非开发人员之间更加紧密地合作。 + +这对开发人员意味着什么?仅仅成为技术专家已经不够了。你必须学会有效地与他人合作。 + +协作不仅仅是提问和回答问题或参加会议。它是与其他人一起卷起袖子,共同解决问题。 + +我是结对编程的忠实粉丝。你可能会称之为“极端协作”。作为一名开发人员,我的技能在结对时得到了提升。如果我在某个领域或技术上比我的结对伙伴弱,我显然可以从他或她的经验中学习。当我在某些方面更强时,通过解释自己,我会更清楚地了解自己所知道和不知道的东西。无论如何,我们双方都会为合作带来一些东西,并互相学习。 + +在结对编程时,我们每个人都把我们集体的编程经验——无论是领域知识还是技术——带到手头的问题中,并可以为高效编写软件带来独特的见解和经验。即使在领域或技术知识极度不平衡的情况下,经验更丰富的参与者也总是能从对方那里学到一些东西——也许是一个新的键盘快捷键,或者接触到一个新的工具或库。对于结对中经验较少的成员来说,这是一个快速上手的好方法。 + +结对编程在敏捷软件开发的拥护者中很受欢迎,尽管它并不局限于他们。一些反对结对编程的人可能会问:“为什么要付两个程序员的钱来做一个人的工作?”我的回答是,确实,你不应该这样做。我认为结对编程可以提高质量、对领域和技术的理解、技巧(如IDE技巧),并减轻“彩票风险”的影响(你的一位专家开发人员中了彩票,第二天就辞职了)。 + +学习一个新的键盘快捷键的长期价值是什么?我们如何衡量结对编程对产品整体质量的提升?我们如何衡量你的伙伴不让你采用一种死胡同的方法来解决一个难题的影响?一项研究引用了40%的有效性和速度提升(J T Nosek,“协作编程的案例”,*ACM通讯*,1998年3月)。减轻“彩票风险”的价值是什么?这些收益大多难以衡量。 + +谁应该和谁结对?如果你是团队的新成员,找到一个知识渊博的团队成员很重要。同样重要的是找到一个具有良好人际交往和指导能力的人。如果你没有太多的领域经验,那就与一个在领域内是专家的团队成员结对。 + +如果你还不信服,可以尝试一下:与你的同事合作。在一个有趣且棘手的问题上结对。看看感觉如何。多试几次。 + +作者:[Adrian Wible](http://programmer.97things.oreilly.com/wiki/index.php/Adrian_Wible) \ No newline at end of file diff --git a/zh/thing_86/README.md b/zh/thing_86/README.md new file mode 100644 index 00000000..8c845a40 --- /dev/null +++ b/zh/thing_86/README.md @@ -0,0 +1,23 @@ +# 两个错误可以造就一个正确(并且难以修复) + +代码从不说谎,但它可能会自相矛盾。有些矛盾会导致那些“这怎么可能工作?”的时刻。 + +在一次[采访](http://www.netjeff.com/humor/item.cgi?file=ApolloComputer)中,阿波罗11号登月舱软件的首席设计师Allan Klumpp透露,控制引擎的软件中存在一个本应使着陆器不稳定的错误。然而,另一个错误弥补了第一个错误,因此在发现或修复这两个错误之前,该软件被用于阿波罗11号和12号的登月任务。 + +考虑一个返回完成状态的函数。想象一下,它在本应返回true时返回了false。现在想象一下,调用函数忽略了检查返回值。一切正常,直到有一天有人注意到缺少检查并插入了它。 + +或者考虑一个将状态存储为XML文档的应用程序。想象一下,其中一个节点被错误地写为`TimeToLive`,而不是文档中所说的`TimeToDie`。当写入代码和读取代码都包含相同的错误时,一切看起来都很好。但是修复其中一个,或者添加一个读取同一文档的新应用程序,对称性就被打破了,代码也随之出现问题。 + +当代码中的两个缺陷导致一个可见的故障时,系统化的故障修复方法本身可能会失效。开发者收到一个错误报告,找到缺陷,修复它,并重新测试。然而,报告的故障仍然存在,因为第二个缺陷在起作用。因此,第一个修复被撤销,代码被检查直到找到第二个潜在缺陷,并应用修复。但第一个缺陷又回来了,报告的故障仍然存在,因此第二个修复被回滚。这个过程重复进行,但现在开发者已经排除了两个可能的修复方案,并正在寻找一个永远不会起作用的第三个方案。 + +两个代码缺陷之间的相互作用表现为一个可见的故障,这不仅使问题难以修复,还会让开发者走入死胡同,最终发现他们早先尝试的解决方案是正确的。 + +这种情况不仅发生在代码中:问题也存在于书面需求文档中。而且它可以像病毒一样从一个地方传播到另一个地方。代码中的错误弥补了书面描述中的错误。 + +它也可以传播到人身上:用户学会了当应用程序说“左”时意味着“右”,因此他们相应地调整自己的行为。他们甚至将其传递给新用户:“记住,当那个应用程序说点击左键时,它实际上是指右边的按钮。”修复错误后,突然之间用户需要重新培训。 + +单一的错误可能很容易发现和修复。那些由多个原因引起、需要多次更改的问题则更难解决。部分原因是简单的问题很容易修复,以至于人们倾向于相对快速地解决它们,而将更困难的问题留到以后解决。 + +关于如何处理由相互关联的缺陷引起的故障,没有简单的建议。需要意识到这种可能性,保持清醒的头脑,并愿意考虑所有可能性。 + +作者:[Allan Kelly](http://programmer.97things.oreilly.com/wiki/index.php/Allan_Kelly) \ No newline at end of file diff --git a/zh/thing_87/README.md b/zh/thing_87/README.md new file mode 100644 index 00000000..088469bb --- /dev/null +++ b/zh/thing_87/README.md @@ -0,0 +1,15 @@ +# 为朋友编写代码的Ubuntu精神 + +我们经常独自编写代码,这些代码反映了我们对问题的个人理解以及非常个性化的解决方案。我们可能是团队的一部分,但我们却是孤立的,团队也是如此。我们太容易忘记,这些孤立编写的代码将被他人执行、使用、扩展和依赖。我们很容易忽视软件创建的社会性。创建软件是一项技术活动,同时也是一项社会活动。我们只需要更频繁地抬起头来,意识到我们并不是在孤立地工作,我们有共同的责任去提高每个人的成功概率,而不仅仅是开发团队。 + +你可以独自编写高质量的代码,同时沉浸在自我中。从某种角度来看,这是一种以自我为中心的方法(这里的“自我”不是指傲慢,而是指个人)。这也是一种禅宗的观点,它关乎你在编写代码的那一刻。我总是试图活在当下,因为这有助于我更接近高质量,但我是活在我自己的当下。那么我的团队的当下呢?我的当下和团队的当下是一样的吗? + +在祖鲁语中,Ubuntu的哲学可以总结为“Umuntu ngumuntu ngabantu”,大致翻译为“一个人是通过他人成为人的。” 我变得更好是因为你通过你的善行使我变得更好。反过来,如果我做得不好,你也会因此做得更糟。在开发者中,我们可以将其缩小为“一个开发者是通过其他开发者成为开发者的。” 如果我们深入到底层,那么“代码是通过其他代码成为代码的。” + +我编写的代码质量会影响你编写的代码质量。如果我的代码质量很差怎么办?即使你编写了非常干净的代码,在你使用我的代码的地方,你的代码质量也会下降到接近我的代码质量。你可以应用许多模式和技术来限制损害,但损害已经造成。仅仅因为我在自己的当下没有考虑到你,我让你做了比你本应做的更多的事情。 + +我可能认为我的代码是干净的,但我仍然可以通过Ubuntu编码使其变得更好。Ubuntu代码是什么样的?它看起来就像干净的好代码。这不是关于代码本身,而是关于创建代码的行为。为你的朋友编写代码,带着Ubuntu精神,将帮助你的团队践行你的价值观并强化你的原则。下一个以任何方式接触你代码的人,都会成为一个更好的人和更好的开发者。 + +禅宗关乎个人。Ubuntu关乎一群人的禅宗。我们很少为自己单独编写代码。 + +作者:[Aslam Khan](http://programmer.97things.oreilly.com/wiki/index.php/Aslam_Khan) \ No newline at end of file diff --git a/zh/thing_88/README.md b/zh/thing_88/README.md new file mode 100644 index 00000000..5da1313b --- /dev/null +++ b/zh/thing_88/README.md @@ -0,0 +1,31 @@ +# Unix工具是你的朋友 + +如果我不得不在流放到荒岛时选择带一个IDE还是Unix工具集,我会毫不犹豫地选择Unix工具。以下是你应该熟练掌握Unix工具的原因。 + +首先,IDE针对特定语言,而Unix工具可以处理任何以文本形式出现的内容。在当今每年都有新语言和符号涌现的开发环境中,学习以Unix方式工作是一项会反复带来回报的投资。 + +此外,虽然IDE只提供其开发者设想的命令,但使用Unix工具,你可以执行任何你能想象的任务。将它们视为(经典的前Bionicle)乐高积木:你只需通过组合小巧但功能强大的Unix工具来创建自己的命令。例如,以下序列是基于文本的Cunningham签名分析的实现——每个文件的分号、大括号和引号的序列,可以揭示文件内容的很多信息。 + +``` +for i in *.java; do + echo -n "$i: " + sed 's/[^"{};]//g' $i | tr -d '\n' + echo +done +``` + +此外,你学习的每个IDE操作都是特定于该任务的;例如,在项目的调试构建配置中添加一个新步骤。相比之下,提高你的Unix工具技能可以使你在任何任务中更有效率。举个例子,我使用了前面命令序列中的sed工具来改变项目的构建,以便在多个处理器架构上进行交叉编译。 + +Unix工具是在多用户计算机只有128kB内存的时代开发的。它们设计中的巧妙之处意味着如今它们可以非常高效地处理庞大的数据集。大多数工具像过滤器一样工作,一次只处理一行数据,这意味着它们可以处理的数据量没有上限。你想在半TB的英文维基百科转储中搜索编辑次数吗?只需简单调用 + +``` +grep '' | wc –l +``` + +就能轻松得到答案。如果你发现一个命令序列通常有用,你可以轻松地将其打包成一个shell脚本,使用一些特别强大的编程结构,比如将数据管道传输到循环和条件语句中。更令人印象深刻的是,像前面那样的Unix命令作为管道执行时,会自然地将其负载分布到现代多核CPU的多个处理单元上。 + +Unix工具的小巧美观和开源实现使它们无处不在,即使在资源受限的平台上,比如我的机顶盒媒体播放器或DSL路由器。这些设备不太可能提供强大的图形用户界面,但它们通常包含BusyBox应用程序,该应用程序提供了最常用的工具。如果你在Windows上开发,Cygwin环境为你提供了所有可以想象的Unix工具,既有可执行文件形式,也有源代码形式。 + +最后,如果现有的工具都不符合你的需求,扩展Unix工具的世界非常容易。只需编写一个程序(用你喜欢的任何语言),遵循一些简单的规则:你的程序应该只执行一个任务;它应该从其标准输入中读取文本行数据;并且它应该在其标准输出上显示结果,不带标题和其他噪音。影响工具操作的参数在命令行中给出。遵循这些规则,“地球及其中的一切都属于你”。 + +作者:[Diomidis Spinellis](http://programmer.97things.oreilly.com/wiki/index.php/Diomidis_Spinellis) \ No newline at end of file diff --git a/zh/thing_89/README.md b/zh/thing_89/README.md new file mode 100644 index 00000000..be739998 --- /dev/null +++ b/zh/thing_89/README.md @@ -0,0 +1,37 @@ +# 使用正确的算法和数据结构 + +> 一家拥有许多分支机构的大银行抱怨说,它为新柜员购买的计算机速度太慢。这是在电子银行普及之前,自动取款机(ATM)还没有像现在这样普遍的时代。人们会频繁地访问银行,而缓慢的计算机使得人们排起了长队。因此,银行威胁要终止与供应商的合同。 + +> 供应商派了一名性能分析和调优专家来确定延迟的原因。他很快发现一个特定的程序在终端上运行,几乎消耗了所有的CPU资源。使用分析工具,他深入研究了该程序,并找到了罪魁祸首的函数。源代码是这样的: + +> ``` +> for (i=0; i if (... s[i] ...) ... +> } +> ``` + +> 而字符串`s`平均有数千个字符长。这段代码(由银行编写)很快被修改,银行柜员从此过上了幸福的生活…… + +难道程序员不应该做得更好,避免使用这种不必要地具有二次方复杂度的代码吗? +每次调用`strlen`都会遍历字符串中的数千个字符以找到其终止的空字符。然而,字符串从未改变。通过在循环之前确定字符串的长度,程序员可以节省数千次`strlen`调用(以及数百万次循环执行): + +``` +n=strlen(s); +for (i=0; i allCustomers = new ArrayList(); + // ... + public ArrayList findCustomersThatSpendAtLeast(Money amount) { + ArrayList customersOfInterest = new ArrayList(); + for (Customer customer: allCustomers) { + if (customer.spendsAtLeast(amount)) + customersOfInterest.add(customer); + } + return customersOfInterest; + } +} +``` + +通过将这个原始集合暴露给客户端,我们违反了封装原则。这不仅限制了我们重构的能力,还迫使我们代码的用户通过重新实现可能相同的查询来违反 DRY 原则。这种情况可以通过从 API 中移除暴露的原始集合来轻松避免。在这个例子中,我们可以引入一个新的、特定领域的集合类型,称为 `CustomerList`。这个新类在语义上更符合我们的领域。它将作为我们所有查询的自然归宿。 + +拥有这个新的集合类型还将使我们能够轻松地看到这些查询是否是性能瓶颈。通过将查询纳入类中,我们消除了向客户端暴露表示选择(如 `ArrayList`)的需要。这使我们能够自由地更改这些实现,而不必担心违反客户端合同: + +```java +public class CustomerList { + private ArrayList customers = new ArrayList(); + private SortedList customersSortedBySpendingLevel = new SortedList(); + // ... + public CustomerList findCustomersThatSpendAtLeast(Money amount) { + return new CustomerList(customersSortedBySpendingLevel.elementsLargerThan(amount)); + } +} + +public class UsageExample { + public static void main(String[] args) { + CustomerList customers = new CustomerList(); + // ... + CustomerList customersOfInterest = customers.findCustomersThatSpendAtLeast(someMinimalAmount); + // ... + } +} +``` + +在这个例子中,遵循 DRY 原则使我们能够引入一个替代的索引方案,使用 `SortedList` 根据客户的消费水平进行索引。比这个特定例子的具体细节更重要的是,遵循 DRY 原则帮助我们找到并修复了一个性能瓶颈,如果代码是 WET 的,这个瓶颈将更难被发现。 + +作者:[Kirk Pepperdine](http://programmer.97things.oreilly.com/wiki/index.php/Kirk_Pepperdine) \ No newline at end of file diff --git a/zh/thing_92/README.md b/zh/thing_92/README.md new file mode 100644 index 00000000..b718e600 --- /dev/null +++ b/zh/thing_92/README.md @@ -0,0 +1,13 @@ +# 当程序员与测试员协作时 + +当测试员和程序员开始协作时,神奇的事情发生了。通过缺陷跟踪系统来回发送缺陷的时间减少了。浪费在试图弄清楚某件事是真正的缺陷还是新功能上的时间减少了,更多的时间被用于开发满足客户期望的优秀软件。甚至在编码开始之前,就有许多机会可以开始协作。 + +测试员可以帮助客户使用他们领域的语言编写和自动化验收测试,使用诸如Fit(集成测试框架)等工具。当这些测试在编码开始之前提供给程序员时,团队正在实践验收测试驱动开发(ATDD)。程序员编写夹具来运行测试,然后编写代码以使测试通过。这些测试随后成为回归测试套件的一部分。当这种协作发生时,功能测试会提前完成,从而有时间进行探索性测试,以应对边缘条件或通过更大范围的工作流。 + +我们可以更进一步。作为测试员,我可以在程序员开始编码新功能之前提供大部分的测试想法。当我询问程序员是否有任何建议时,他们几乎总是会提供一些信息,帮助我实现更好的测试覆盖,或者帮助我避免在不必要的测试上花费大量时间。通常,我们通过测试澄清了许多初始想法,从而防止了缺陷的产生。例如,在我参与的一个项目中,我提供给程序员的Fit测试展示了查询的预期结果,以响应通配符搜索。程序员原本只打算编写完整单词搜索的代码。我们能够在编码开始之前与客户交谈并确定正确的解释。通过协作,我们防止了缺陷的产生,这为我们节省了大量时间。 + +程序员也可以与测试员协作,创建成功的自动化测试。他们理解良好的编码实践,可以帮助测试员建立一个适用于整个团队的健壮测试自动化套件。我经常看到测试自动化项目失败,因为测试设计得不好。测试试图测试太多内容,或者测试员对技术的理解不够,无法保持测试的独立性。测试员通常是瓶颈,因此程序员与他们合作完成自动化等任务是有意义的。与测试员合作,了解可以早期测试的内容,也许通过提供一个简单的工具,将为程序员提供另一个反馈周期,从长远来看,这将帮助他们交付更好的代码。 + +当测试员不再认为他们的唯一工作是破坏软件并发现程序员代码中的缺陷时,程序员也不再认为测试员是“针对他们”,并且更愿意协作。当程序员开始意识到他们负责在代码中构建质量时,代码的可测试性自然成为副产品,团队可以一起自动化更多的回归测试。成功的团队协作的魔力由此开始。 + +作者:[Janet Gregory](http://programmer.97things.oreilly.com/wiki/index.php/Janet_Gregory) \ No newline at end of file diff --git a/zh/thing_93/README.md b/zh/thing_93/README.md new file mode 100644 index 00000000..de24b093 --- /dev/null +++ b/zh/thing_93/README.md @@ -0,0 +1,13 @@ +# 编写代码时,假设你需要终身维护它 + +你可以询问97个人,每个程序员应该知道什么和做什么,你可能会得到97个不同的答案。这可能会让人感到既不知所措又害怕。所有的建议都是好的,所有的原则都是合理的,所有的故事都引人入胜,但你该从哪里开始呢?更重要的是,一旦你开始了,你如何跟上你学到的所有最佳实践,并如何使它们成为你编程实践中不可或缺的一部分? + +我认为答案在于你的心态,或者更简单地说,在于你的态度。如果你不关心你的开发同事、测试人员、经理、销售和市场人员以及最终用户,那么你就不会受到驱动去采用测试驱动开发或在代码中编写清晰的注释。我认为有一种简单的方法可以调整你的态度,并始终受到驱动去交付最高质量的产品: + +> *编写代码时,假设你需要终身维护它。* + +就是这样。如果你接受这个观念,许多美好的事情将会发生。如果你接受你以前或现在的雇主有权在半夜打电话给你,要求你解释你在编写`fooBar`方法时所做的选择,你会逐渐进步,成为一名专家程序员。你自然会想要想出更好的变量和方法名。你会避免编写包含数百行代码的代码块。你会寻找、学习并使用设计模式。你会编写注释,测试你的代码,并不断进行重构。终身维护你编写的所有代码也应该是一项可扩展的工作。因此,你别无选择,只能变得更好、更聪明、更高效。 + +如果你仔细想想,你多年前编写的代码仍然会影响你的职业生涯,无论你是否喜欢。你设计的每一个方法、类和模块都会留下你的知识、态度、坚韧、专业精神、承诺程度和享受程度的痕迹。人们会根据他们看到的代码对你形成看法。如果这些看法总是负面的,你将无法从职业生涯中获得你期望的收获。用每一行代码来关心你的职业生涯、你的客户和你的用户——编写代码时,假设你需要终身维护它。 + +作者:[Yuriy Zubarev](http://programmer.97things.oreilly.com/wiki/index.php/Yuriy_Zubarev) \ No newline at end of file diff --git a/zh/thing_94/README.md b/zh/thing_94/README.md new file mode 100644 index 00000000..7fe0c42a --- /dev/null +++ b/zh/thing_94/README.md @@ -0,0 +1,26 @@ +# 使用示例编写小型函数 + +我们希望编写正确的代码,并且手头有证据证明其正确性。思考函数的“大小”有助于解决这两个问题。这里的“大小”并不是指实现函数的代码量——尽管这也很重要——而是指我们的代码所体现的数学函数的大小。 + +例如,在围棋游戏中,有一种情况叫做*打吃*(atari),即一方的棋子可能会被对手捕获:如果一个棋子有两个或更多的自由空间(称为*气*),那么它就不处于打吃状态。计算一个棋子有多少气可能比较复杂,但如果知道了气的数量,判断是否处于打吃状态就很简单了。我们可能会从编写这样的函数开始: + +``` +boolean atari(int libertyCount) + libertyCount < 2 +``` + +这个函数比看起来要大。数学函数可以理解为一个集合,它是其定义域(这里是`int`)和值域(这里是`boolean`)的笛卡尔积的某个子集。如果这些值的集合与Java中的大小相同,那么集合`int×boolean`中将有`2L*(Integer.MAX_VALUE+(-1L*Integer.MIN_VALUE)+1L)`或8,589,934,592个成员。其中一半是我们函数的子集成员,因此为了提供完整的证据证明我们的函数是正确的,我们需要检查大约4.3×109个示例。 + +这就是测试无法证明没有bug的本质。测试可以展示功能的存在,但我们仍然面临这个大小的问题。 + +问题领域帮助我们解决了这个问题。围棋的性质意味着一个棋子的气数并不是任意的整数,而是{1,2,3,4}中的一个。因此,我们可以这样写: + +``` +LibertyCount = {1,2,3,4} +boolean atari(LibertyCount libertyCount) + libertyCount == 1 +``` + +这样处理起来就容易多了:现在计算的函数是一个最多有八个成员的集合。事实上,四个检查过的示例就足以完全确定函数是正确的。这就是为什么使用与问题领域密切相关的类型来编写程序,而不是使用原生类型,是一个好主意。使用受领域启发的类型通常可以使我们的函数变得更小。找出这些类型应该是什么的一种方法是在编写函数之前,以问题领域的术语找出要检查的示例。 + +作者:[Keith Braithwaite](http://programmer.97things.oreilly.com/wiki/index.php/Keith_Braithwaite) \ No newline at end of file diff --git a/zh/thing_95/README.md b/zh/thing_95/README.md new file mode 100644 index 00000000..e651f77c --- /dev/null +++ b/zh/thing_95/README.md @@ -0,0 +1,15 @@ +# 为人编写测试 + +你正在为部分或全部的生产代码编写自动化测试。恭喜你!你是在编写代码之前编写测试吗?那就更好了!仅仅做到这一点,你就已经站在了软件工程实践的前沿,成为早期采用者之一。但是,你编写的测试是好测试吗?如何判断呢?一种方法是问自己:“我为谁编写这些测试?”如果答案是“为我自己,为了省去修复bug的麻烦”或“为编译器,以便它们可以执行”,那么很可能你并没有编写出最好的测试。那么,你应该为谁编写测试呢?**为那些试图理解你代码的人**。 + +好的测试就像是代码的文档。它们描述了代码的工作原理。对于每个使用场景,测试应该: + +1. 描述上下文、起点或必须满足的前提条件 +2. 展示软件是如何被调用的 +3. 描述预期的结果或需要验证的后置条件 + +不同的使用场景在这些方面会有略微不同的版本。试图理解你代码的人应该能够通过查看几个测试,并比较这些测试的这三个部分,来了解是什么导致软件行为的不同。每个测试都应该清晰地展示这三个部分之间的因果关系。这意味着测试中不可见的部分与可见的部分同样重要。测试中过多的代码会让读者分心,关注不重要的细节。尽可能将这些细节隐藏在有意义的方法调用背后——**提取方法**重构是你最好的朋友。确保为每个测试起一个有意义的名称,描述特定的使用场景,这样测试的读者就不必通过逆向工程来理解各种场景。测试类和类方法的名称之间至少应该包括起点和软件的调用方式。这样可以通过快速扫描方法名称来验证测试覆盖率。如果不会导致名称过长,也可以在测试方法名称中包含预期结果。 + +测试你的测试也是一个好主意。你可以通过将错误插入生产代码(当然是你自己的私有副本,之后会丢弃)来验证它们是否能检测到你认为它们能检测到的错误。确保它们以有帮助且有意义的方式报告错误。你还应该验证你的测试是否清晰地传达给试图理解你代码的人。唯一的方法是让不熟悉你代码的人阅读你的测试,并告诉你他们学到了什么。仔细倾听他们的话。如果他们不清楚某些内容,很可能不是因为他们不够聪明,而是因为你表达得不够清晰。(你也可以反过来阅读他们的测试!) + +作者:[Gerard Meszaros](http://programmer.97things.oreilly.com/wiki/index.php/Gerard_Meszaros) \ No newline at end of file diff --git a/zh/thing_96/README.md b/zh/thing_96/README.md new file mode 100644 index 00000000..1ddc6eb1 --- /dev/null +++ b/zh/thing_96/README.md @@ -0,0 +1,23 @@ +# 你必须关心代码 + +不需要福尔摩斯也能看出,优秀的程序员编写优秀的代码。糟糕的程序员……则不然。他们制造出怪物,而其他人不得不去清理。你想写出优秀的代码,对吧?你想成为一名优秀的程序员。 + +优秀的代码不会凭空出现。它不是靠运气或行星排列就能产生的。要写出优秀的代码,你必须付出努力。而且,只有在你真正关心优秀代码的情况下,你才能写出优秀的代码。 + +优秀的编程不仅仅源于技术能力。我见过一些非常聪明的程序员,他们能够编写出复杂而令人印象深刻的算法,对语言标准了如指掌,但他们写出的代码却非常糟糕。读起来痛苦,用起来痛苦,修改起来也痛苦。我也见过一些更谦逊的程序员,他们坚持编写非常简单的代码,但写出的程序却优雅而富有表现力,使用起来是一种享受。 + +基于我在软件工厂多年的经验,我得出了一个结论:普通程序员和优秀程序员之间的真正区别在于:**态度**。优秀的编程在于采取一种专业的态度,并希望在现实世界的限制和压力下,编写出最好的软件。 + +*通往地狱的道路是由善意铺就的。* 要成为一名优秀的程序员,你必须超越善意,真正**关心**代码——培养积极的视角和健康的态度。优秀的代码是由大师级工匠精心打造的,而不是由马虎的程序员随意拼凑的,也不是自诩为编码大师的人神秘地搭建的。 + +你想写出优秀的代码。你想成为一名优秀的程序员。所以,你关心代码: + +- 在任何编码情况下,你都拒绝编写那些只是看起来能工作的东西。你努力编写优雅的代码,这些代码显然是正确的(并且有良好的测试来证明其正确性)。 +- 你编写的代码是**可发现的**(其他程序员可以轻松理解),是**可维护的**(你或其他程序员将来可以轻松修改),并且是正确的(你采取一切可能的步骤来确定你已经解决了问题,而不仅仅是让程序看起来能工作)。 +- 你与其他程序员合作良好。没有程序员是一座孤岛。很少有程序员独自工作;大多数程序员要么在公司环境中,要么在开源项目中与团队合作。你考虑其他程序员,并编写出其他人可以阅读的代码。你希望团队编写出最好的软件,而不是让自己看起来聪明。 +- 每次你接触一段代码时,你都努力让它比你发现时更好(无论是结构更好、测试更完善、更易理解……)。 +- 你关心代码和编程,所以你不断学习新的语言、习惯用法和技术。但你只在适当的时候应用它们。 + +幸运的是,你正在阅读这些建议,因为你确实关心代码。你对它感兴趣。这是你的激情所在。享受编程的乐趣。享受编写代码解决棘手问题的过程。编写出让你自豪的软件。 + +作者:[Pete Goodliffe](http://programmer.97things.oreilly.com/wiki/index.php/Pete_Goodliffe) \ No newline at end of file diff --git a/zh/thing_97/README.md b/zh/thing_97/README.md new file mode 100644 index 00000000..4e421bbb --- /dev/null +++ b/zh/thing_97/README.md @@ -0,0 +1,13 @@ +# 你的客户并不总是言如其意 + +我从未遇到过不愿意告诉我他们想要什么的客户——通常还会非常详细地描述。问题是,客户并不总是告诉你全部真相。他们通常不会撒谎,但他们用的是客户的语言,而不是开发者的语言。他们使用自己的术语和背景。他们会遗漏重要的细节。他们假设你已经像他们一样在他们的公司工作了20年。更复杂的是,许多客户实际上并不知道他们一开始想要什么!有些人可能对“大局”有所把握,但他们很少能有效地传达他们愿景的细节。其他人可能对完整的愿景了解较少,但他们知道自己不想要什么。那么,你如何能向一个没有告诉你他们真正想要什么的人交付一个软件项目呢?其实很简单。多与他们互动。 + +尽早并经常挑战你的客户。不要简单地用他们的话重述他们告诉你的需求。记住:他们告诉你的并不是他们真正的意思。我经常在与他们的对话中替换词汇,并观察他们的反应。你会惊讶地发现,*客户*和*客户方*这两个词的含义完全不同。然而,告诉你他在软件项目中想要什么的人会交替使用这些术语,并期望你能分辨出他在谈论哪一个。你会感到困惑,而你编写的软件也会因此受到影响。 + +在你确定自己理解了客户的需求之前,多次与他们讨论相关主题。尝试与他们一起重述问题两到三次。与他们讨论在你正在谈论的主题之前或之后发生的事情,以获得更好的背景信息。如果可能的话,让多个人在不同的对话中告诉你同一个主题。他们几乎总是会告诉你不同的故事,这将揭示出独立但相关的事实。两个人告诉你同一个主题时,往往会互相矛盾。你成功的最佳机会是在开始复杂的软件制作之前,先解决这些差异。 + +在对话中使用视觉辅助工具。这可以简单到在会议中使用白板,容易到在设计阶段早期创建视觉模型,或复杂到制作功能性原型。众所周知,在对话中使用视觉辅助工具有助于延长我们的注意力,并提高信息的保留率。利用这一点,为你的项目成功做好准备。 + +在我过去的生活中,我曾是一个制作华丽项目的团队中的“多媒体程序员”。我们的一个客户详细描述了他们对项目外观和感觉的想法。在设计会议中讨论的整体配色方案表明演示文稿的背景是黑色的。我们以为我们已经搞定了。图形设计师团队开始制作数百个分层的图形文件。大量的时间被用来塑造最终产品。当我们向客户展示我们的劳动成果时,一个令人震惊的发现出现了。当她看到产品时,她对背景颜色的原话是“当我说黑色时,我指的是白色。”所以,你看,事情从来不像黑白那样简单。 + +作者:[Nate Jackson](http://programmer.97things.oreilly.com/wiki/index.php/Icnatejackson) \ No newline at end of file