Skip to content

Commit

Permalink
Merge pull request lingcoder#141 from 1326670425/master
Browse files Browse the repository at this point in the history
附录:编程指南 翻译完成
  • Loading branch information
lingcoder authored Jul 18, 2019
2 parents 589d98d + 937d2b7 commit e5da738
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 2 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
- [ ] [第二十四章 并发编程](docs/book/24-Concurrent-Programming.md)
- [ ] [第二十五章 设计模式](docs/book/25-Patterns.md)
- [x] [附录:补充](docs/book/Appendix-Supplements.md)
- [ ] [附录:编程指南](docs/book/Appendix-Programming-Guidelines.md)
- [x] [附录:编程指南](docs/book/Appendix-Programming-Guidelines.md)
- [ ] [附录:文档注释](docs/book/Appendix-Javadoc.md)
- [ ] [附录:对象传递和返回](docs/book/Appendix-Passing-and-Returning-Objects.md)
- [ ] [附录:流式IO](docs/book/Appendix-IO-Streams.md)
Expand Down
72 changes: 71 additions & 1 deletion docs/book/Appendix-Programming-Guidelines.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

8. **在编写类之前,先编写测试代码,以验证类的设计是完善的**。如果不编写测试代码,那么就不知道类是什么样的。此外,通过编写测试代码,往往能够激发出类中所需的其他功能或约束。而这些功能或约束并不总是出现在分析和设计过程中。测试还会提供示例代码,显示了如何使用这个类。

9. **所有的软件设计问题,都可以通过引入一个额外的间接概念层次(extra level of conceptual indirection)来解决**这个软件工程的基本规则是抽象的基础,是面向对象编程的主要特征。在面向对象编程中,我们也可以这样说:“如果你的代码太复杂,就要生成更多的对象。”
9. **所有的软件设计问题,都可以通过引入一个额外的间接概念层次(extra level of conceptual indirection)来解决**这个软件工程的基本规则[^1]是抽象的基础,是面向对象编程的主要特征。在面向对象编程中,我们也可以这样说:“如果你的代码太复杂,就要生成更多的对象。”

10. **间接(indirection)应具有意义(与准则9一致)**。这个含义可以简单到“将常用代码放在单个方法中。”如果添加没有意义的间接(抽象,封装等)级别,那么它就像没有足够的间接性那样糟糕。

Expand Down Expand Up @@ -85,7 +85,77 @@
<!-- Implementation -->
## 实现

36. **遵循编码惯例**。有很多不同的约定,例如,[谷歌使用的约定](https://google.github.io/styleguide/javaguide.html)(本书中的代码尽可能地遵循这些约定)。如果坚持使用其他语言的编码风格,那么读者就会很难去阅读。无论决定采用何种编码约定,都要确保它们在整个项目中保持一致。集成开发环境通常包含内置的重新格式化(reformatter)和检查器(checker)。

37. **无论使用何种编码风格,如果你的团队(甚至更好是公司)对其进行标准化,它就确实会产生重大影响**。这意味着,如果不符合这个标准,那么每个人都认为修复别人的编码风格是公平的游戏。标准化的价值在于解析代码可以花费较少的脑力,因此可以更专注于代码的含义。

38. **遵循标准的大写规则**。类名的第一个字母大写。字段,方法和对象(引用)的第一个字母应为小写。所有标识符应该将各个单词组合在一起,并将所有中间单词的首字母大写。例如:

- **ThisIsAClassName**
- **thisIsAMethodOrFieldName**

**static final** 类型的标识符的所有字母全部大写,并用下划线分隔各个单词,这些标识符在其定义中具有常量初始值。这表明它们是编译时常量。
- **包是一个特例**,它们都是小写的字母,即使是中间词。域扩展(com,org,net,edu等)也应该是小写的。这是Java 1.1和Java 2之间的变化。

39. **不要创建自己的“装饰”私有字段名称**。这通常以前置下划线和字符的形式出现。匈牙利命名法(译者注:一种命名规范,基本原则是:变量名=属性+类型+对象描述。Win32程序风格采用这种命名法,如`WORD wParam1;LONG lParam2;HANDLE hInstance`)是最糟糕的例子,你可以在其中附加额外字符用于指示数据类型,用途,位置等,就好像你正在编写汇编语言一样,编译器根本没有提供额外的帮助。这些符号令人困惑,难以阅读,并且难以执行和维护。让类和包来指定名称范围。如果认为必须装饰名称以防止混淆,那么代码就可能过于混乱,这应该被简化。

40. 在创建一般用途的类时,**遵循“规范形式”**。包括**equals()****hashCode()****toString()****clone()**的定义(实现**Cloneable**,或选择其他一些对象复制方法,如序列化),并实现**Comparable****Serializable**

41. **对读取和更改私有字段的方法使用“get”,“set”和“is”命名约定**。这种做法不仅使类易于使用,而且也是命名这些方法的标准方法,因此读者更容易理解。

42. **对于所创建的每个类,请包含该类的JUnit测试**(请参阅*junit.org*以及[第十六章:代码校验]()中的示例)。无需删除测试代码即可在项目中使用该类,如果进行更改,则可以轻松地重新运行测试。测试代码也能成为如何使用这个类的示例。

43. **有时需要继承才能访问基类的protected成员**。这可能导致对多种基类型的感知需求(perceived need)。如果不需要向上转型,则可以首先派生一个新类来执行受保护的访问。然后把该新类作为使用它的任何类中的成员对象,以此来代替直接继承。

44. **为了提高效率,避免使用*final*方法**。只有在分析后发现方法调用是瓶颈时,才将**final**用于此目的。

45. **如果两个类以某种功能方式相互关联(例如集合和迭代器),则尝试使一个类成为另一个类的内部类**。这不仅强调了类之间的关联,而且通过将类嵌套在另一个类中,可以允许在单个包中重用类名。Java集合库通过在每个集合类中定义内部**Iterator**类来实现此目的,从而为集合提供通用接口。使用内部类的另一个原因是作为**私有**实现的一部分。这里,内部类将有利于实现隐藏,而不是上面提到的类关联和防止命名空间污染。

46. **只要你注意到类似乎彼此之间具有高耦合,请考虑如果使用内部类可能获得的编码和维护改进**。内部类的使用不会解耦类,而是明确耦合关系,并且更方便。

47. **不要成为过早优化的牺牲品**。过早优化是很疯狂的行为。特别是,不要担心编写(或避免)本机方法(native methods),将某些方法设置为**final**,或者在首次构建系统时调整代码以使其高效。你的主要目标应该是验证设计。即使设计需要一定的效率,也*先让它工作,然后再让它变快*

48. **保持作用域尽可能小,以便能见度和对象的寿命尽可能小**。这减少了在错误的上下文中使用对象并隐藏了难以发现的bug的机会。例如,假设有一个集合和一段迭代它的代码。如果复制该代码以用于一个新集合,那么可能会意外地将旧集合的大小用作新集合的迭代上限。但是,如果旧集合比较大,则会在编译时捕获错误。

49. **使用标准Java库中的集合**。熟练使用它们,将会大大提高工作效率。首选**ArrayList**用于序列,**HashSet**用于集合,**HashMap**用于关联数组,**LinkedList**用于堆栈(而不是**Stack**,尽管也可以创建一个适配器来提供堆栈接口)和队列(也可以使用适配器,如本书所示)。当使用前三个时,将其分别向上转型为**List****Set****Map**,那么就可以根据需要轻松更改为其他实现。

50. **为使整个程序健壮,每个组件必须健壮**。在所创建的每个类中,使用Java所提供的所有工具,如访问控制,异常,类型检查,同步等。这样,就可以在构建系统时安全地进入下一级抽象。

51. **编译时错误优于运行时错误**。尝试尽可能在错误发生点处理错误。在最近的处理程序中尽其所能地捕获它能处理的所有异常。在当前层面处理所能处理的所有异常,如果解决不了,就重新抛出异常。

52. **注意长方法定义**。方法应该是简短的功能单元,用于描述和实现类接口的离散部分。维护一个冗长而复杂的方法是很困难的,而且代价很大,并且这个方法可能是试图做了太多事情。如果看到这样的方法,这表明,至少应该将它分解为多种方法。也可能建议去创建一个新类。小的方法也可以促进类重用。(有时方法必须很大,但它们应该只做一件事。)

53. **保持“尽可能私有”**。一旦公开了你的类库中的一个方面(一个方法,一个类,一个字段),你就永远无法把它拿回来。如果这样做,就将破坏某些人的现有代码,迫使他们重写和重新设计。如果你只公开了必须公开的内容,就可以轻易地改变其他一切,而不会对其他人造成影响,而且由于设计趋于发展,这是一个重要的自由。通过这种方式,更改具体实现将对派生类造成的影响最小。在处理多线程时,私有尤其重要,只有**私有**字段可以防止不同步使用。具有包访问权限的类应该仍然具有**私有**字段,但通常有必要提供包访问权限的方法而不是将它们**公开**

54. **大量使用注释,并使用*Javadoc commentdocumentation*语法生成程序文档**。但是,注释应该为代码增加真正的意义,如果注释只是重申了代码已经清楚表达的内容,这是令人讨厌的。请注意,Java类和方法名称的典型详细信息减少了对某些注释的需求。

55. **避免使用“魔法数字”**。这些是指硬编码到代码中的数字。如果后续必须要更改它们,那将是一场噩梦,因为你永远不知道“100”是指“数组大小”还是“完全不同的东西”。相反,创建一个带有描述性名称的常量并在整个程序中使用常量标识符。这使程序更易于理解,更易于维护。

56. **在创建构造方法时,请考虑异常**。最好的情况是,构造方法不会做任何抛出异常的事情。次一级的最佳方案是,该类仅由健壮的类组成或继承自健壮的类,因此如果抛出异常则不需要处理。否则,必须清除**finally**子句中的组合类。如果构造方法必然失败,则适当的操作是抛出异常,因此调用者不会认为该对象是正确创建的而盲目地继续下去。

57. **在构造方法内部,只需要将对象设置为正确的状态**。主动避免调用其他方法(**final**方法除外),因为这些方法可以被其他人覆盖,从而在构造期间产生意外结果。(有关详细信息,请参阅[第六章:初始化和清理]()章节。)较小,较简单的构造方法不太可能抛出异常或导致问题。

58. **如果类在客户端程序员用完对象时需要进行任何清理,请将清理代码放在一个明确定义的方法中**,并使用像 **dispose()** 这样的名称来清楚地表明其目的。另外,在类中放置一个 **boolean** 标志来指示是否调用了 **dispose()** ,因此 **finalize()** 可以检查“终止条件”(参见[第六章:初始化和清理]()章节)。

59. ***finalize()* 的职责只能是验证对象的“终止条件”以进行调试**。(参见[第六章:初始化和清理]()一章)在特殊情况下,可能需要释放垃圾收集器无法释放的内存。因为可能无法为对象调用垃圾收集器,所以无法使用 **finalize()** 执行必要的清理。为此,必须创建自己的 **dispose()** 方法。在类的 **finalize()** 方法中,检查以确保对象已被清理,如果没有被清理,则抛出一个派生自**RuntimeException**的异常,以指示编程错误。在依赖这样的计划之前,请确保 **finalize()** 适用于你的系统。(可能需要调用 **System.gc()** 来确保此行为。)

60. **如果必须在特定范围内清理对象(除了通过垃圾收集器),请使用以下准则:** 初始化对象,如果成功,立即进入一个带有 **finally** 子句的 **try** 块,并在 **finally**中执行清理操作。

61. **在继承期间覆盖 *finalize()* 时,记得调用 *super.finalize()***。(如果是直接继承自 **Object** 则不需要这样做。)调用 **super.finalize()** 作为重写的 **finalize()** 的最终行为而不是在第一行调用它,这样可以确保基类组件在需要时仍然有效。

62. **创建固定大小的对象集合时,将它们转换为数组,** 尤其是在从方法中返回此集合时。这样就可以获得数组编译时类型检查的好处,并且数组的接收者可能不需要在数组中强制转换对象来使用它们。请注意,集合库的基类 **java.util.Collection** 有两个 **toArray()** 方法来完成此任务。

63. **优先选择 *接口* 而不是 *抽象类***。如果知道某些东西应该是基类,那么第一选择应该是使其成为一个接口,并且只有在需要方法定义或成员变量时才将其更改为抽象类。一个接口关心客户端想要做什么,而一个类倾向于关注(或允许)实现细节。

64. **为了避免非常令人沮丧的经历,请确保类路径中的每个名称只对应一个不在包中的类**。否则,编译器可以首先找到具有相同名称的其他类,并报告没有意义的错误消息。如果你怀疑自己有类路径问题,请尝试在类路径的每个起始点查找具有相同名称的 **.class** 文件。理想情况下,应该将所有类放在包中。

65. **注意意外重载**。如果尝试覆盖基类方法但是拼写错误,则最终会添加新方法而不是覆盖现有方法。但是,这是完全合法的,因此在编译时或运行时不会获得任何错误消息,但代码将无法正常工作。始终使用 **@Override** 注释来防止这种情况。

66. **注意过早优化**。先让它工作,然后再让它变快。除非发现代码的特定部分存在性能瓶颈。除非是使用分析器发现瓶颈,否则过早优化会浪费时间。性能调整所隐藏的额外成本是代码将变得难以理解和维护。

67. **请注意,相比于编写代码,代码被阅读的机会更多**。清晰的设计可能产生易于理解的程序,但注释,详细解释,测试和示例是非常宝贵的,它们可以帮助你和你的所有后继者。如果不出意外,试图从JDK文档中找出有用信息的挫败感应该可以说服你。

[^1]: Andrew Koenig向我解释了它。

<!-- 分页 -->

Expand Down

0 comments on commit e5da738

Please sign in to comment.