1.重新发明轮子1.1.发明家的特质就是要用质疑的心态对待所有事物,你从未停下质疑,那你将不可避免地成为一个发明家1.2.并非所有的事情都有现成的轮子可以拿来用1.3.自己重新写一个新的API,最终调用你使用的库1.3.1.你的API应该是极简的,满足你的需求就可以了1.3.1.1.自己做自己的甲方1.3.2.拥有你自己的支持适配器的方便接口的方法在业界被称为适配器模式(adapter pattern)2.SOLID原则2.1.S:单一责任原则(single-responsibility principle)2.1.1.一个类应该只负责一件事2.1.2.不是一个类做多件事,也就是God类2.1.3.一个类的名字应该尽可能简洁地解释它的功能,而不是含糊不清2.1.4.如果名字太长或太模糊,就需要将该类拆成多个类2.2.O:开放-封闭原则(open-closed principle)2.2.1.一个类应该为扩展而开放,但为修改而封闭2.2.2.类设计成其行为可以被外部修改2.2.3.可扩展性是一个设计决定,有时可能并不可取、不实用,甚至不安全2.3.L:由芭芭拉·利斯科夫(Barbara Liskov)提出的里氏替换原则(Liskov Substitution principle)2.3.1.程序中的对象应该是可以在不改变程序正确性的前提下被它的子类所替换的2.4.I:接口隔离原则(interface segregation principle)2.4.1.偏向于较小的、目标明确的接口,而不是泛化的、范围广泛的接口2.4.2.这是一个不必要的复杂和模糊的建议,甚至可以说是完全错误的2.4.3.分割接口不是基于范围,而是基于设计的实际需求2.4.4.如果单一接口不适合这项工作,请随意拆分它,而不是为了刻意满足某些定死的僵硬教条2.5.D:依赖反转原则(dependency inversion principle)2.5.1.代码不应该依赖具体的实现,而应该依赖抽象2.5.2.继承将你绑定在一个具体的实现上,违反了该原则2.5.3.当你喜欢灵活性并且看到它的价值时,你更喜欢依赖抽象,而在无关紧要的情况下,你会依赖具体实现3.不要使用继承3.1.面向对象编程(Object-Oriented Programming,OOP)最强调的特点是继承3.1.1.在常规的继承中,普通代码和它的变化之间的关系是用父类/子类(ancestor/ descendant)模型来表达的3.2.从长远来看,继承带来的问题比它解决的问题要多3.2.1.多重继承(multiple inheritance)是首要问题之一3.2.2.除了多重继承之外,继承的一个更大的问题是强依赖性3.2.2.1.紧耦合(tight coupling)3.2.2.2.依赖性是万恶之源3.3.组合(composition)3.3.1.你不是从类中继承,而是在你的构造函数中接收它的抽象作为参数3.3.2.把你的组件看成相互拼接的乐高积木块,而不是对象的层次结构3.3.3.组合把共同的功能看成独立的组件3.3.4.组合更像是一种客户端-服务器的关系,而不是父-子关系3.3.5.通过它的引用来调用复用的代码,而不是在你的范围内继承它的方法3.3.6.把类作为参数进行接收还有一个额外的好处,那就是可以通过注入具体实现的模拟版本,让对象更加容易进行单元测试3.3.7.使用组合而不是继承可能需要你多写点代码3.3.7.1.因为你可能需要用接口而不是具体的引用来定义依赖关系,但这也会使代码摆脱依赖关系4.不要使用类4.1.类会产生少量的引用间接开销(reference indirection overhead)4.1.1.与值类型(value type)相比,它们更侧重间接方面4.2.值类型可以是有价值的4.2.1.C#中的原始类型,如int、long和double,就是值类型4.2.2.可以用enum和struct这样的结构来组成你自己的值类型4.3.enum用来保存离散的序数值(discrete ordinal value)是很不错的4.3.1.每一个你定义的enum的类型都是不同的,这让值拥有了类型安全(type-safe)4.3.2.enum也是值类型,也就是说其值和整数值的传递速度是一样快的4.4.类会产生一点存储开销。每个类在实例化的时候都需要保留一个对象头和虚拟方法表4.4.1.类是在堆上分配的,而且它们会被回收4.5.结构是轻量级的类4.5.1.它们被分配在栈上,因为它们是值类型4.5.2.将一个结构值分配给一个变量意味着复制其内容,因为没有一个引用代表它4.5.3.对于任何大于指针大小的数据,复制的速度比仅传递引用的速度要慢4.5.4.因为结构是值类型,将它赋值给另一个结构时,会同时创建该结构所有内容的副本,而不仅仅是创建一个副本内容的引用4.6.在.NET中,栈的大小为1MB,而堆却可以包含TB级的数据4.7.栈的存取速度快,但是如果用大型结构去填充它,它很容易就被填满4.8.调用栈可以非常快速和有效地存储东西4.8.1.由于它们不受垃圾回收的影响,因此它们非常适用于处理较小的值,而且开销也较少4.8.2.因为它们不是引用类型,所以它们也不能为null,这使得结构不可能出现空引用异常4.9.你不能共享对它们的通用引用,这意味着你不能从不同的引用中更改通用实例4.10.当类较大时,可以提供更有效的存储,因为在赋值时只有它们的引用会被复制4.11.请放心地将结构用于不需要继承的小型、不可变的值类型5.糟糕代码5.1.最佳实践来自糟糕的代码,然而糟糕的代码也可能来自最佳实践的胡乱应用5.2.不要使用If/Else5.2.1.If/Else让我们以一种类似于流程图的方式来表达程序的逻辑,但它也会使代码的可读性降低5.2.2.避免不必要缩进的一般原则是尽可能早地退出函数,并且在流程中已经暗示else语义时要避免使用else5.2.3.“愉快路径”(happy path)5.2.3.1.没有其他错误发生时执行的代码部分5.2.4.通过将else语句转换为return语句,我们可以让读者更容易地识别愉快路径,而不是形成if语句的“俄罗斯套娃”5.2.5.尽早验证,并尽早返回5.3.使用goto5.3.1.goto语句可以将程序的执行直接转移到一个任意的目标点5.3.2.许多现代编程语言不再有相当于goto语句的东西5.3.3.C#有一个场景中非常有效:消除函数中多余的退出点5.3.3.1.退出点(exit point)是指函数中导致其返回给调用者的语句5.3.3.2.在C#中,每个返回语句都是一个退出点5.3.3.3.可以使用goto语句来清理,但实际上它在消除冗余方面更有优势5.3.3.4.如果你想在通用退出代码(common exit code)中增加更多的内容,你只需要把它添加到一个地方5.3.3.4.1.通过使用goto语句,我们实际上保持了代码的可读性,减少了缩进,节省了自己的时间,并使将来的修改更加容易,因为我们只需要修改一次5.3.3.5.C# 7.0引入了局部函数,可以用来执行同样的工作,也许是以一种更容易理解的方式5.3.3.6.使用局部函数还允许我们在函数的顶部声明错误处理语句6.不写代码注释6.1.如果代码足够容易理解,则不需要编写代码注释6.2.使用那些无关的注释可能会毁掉代码的可读性6.3.若非必要,不写注释6.4.选个好名字6.4.1.使用HTTP、JSON、ID或者DB等广为人知的缩写倒还是可以的,但千万不要缩短单词6.4.2.当你选择一个描述性的名字时,你不必写一个完整的句子来解释它的功能6.5.充分利用函数6.5.1.短小的函数更易于理解6.5.2.尽量让函数尺寸适合开发人员的屏幕6.5.2.1.阅读代码时,来回滚动屏幕会让你不适,你应该一眼就能看到函数的全貌6.5.3.绝对不要把多个语句放在一行,一个语句至少得占用一行6.6.避免不必要的注释会使有用的注释像珍珠一样闪闪发光。这是使注释有用的唯一方法6.7.写了注释就能让你的代码很容易懂,前提是你的代码本身就精巧、易于理解6.8.公共API6.8.1.用户可能无法接触到代码7.要点7.1.避免因为混淆逻辑上的依赖界限而写出那些刚性代码7.2.不要害怕从头开始做一项工作,因为当你下次做的时候,你会发现进展要快得多7.3.请勇于拆分代码,并修整它7.4.保持代码最新并定期解决它所引起的问题7.5.重复代码而不是复用代码,避免混淆各代码逻辑的作用7.6.把抽象当作一项投资7.6.1.将抽象模型构思得巧妙一些,这样你将来写代码就会花更少的时间7.7.不要让使用的外部库来限制了你的设计7.8.为了避免将代码束缚在特定的层次结构,更推荐组合而不是继承7.9.尽量让代码保持自顶向下的风格,以便于阅读7.10.提前退出函数并避免使用else7.10.1.return7.11.使用goto语句7.11.1.使用一个本地函数来将公共代码保存在一个地方7.12.避免随意、多余的注释7.12.1.只写有用的注释7.13.利用好变量和函数本身命名,让你写的代码更具可读性7.14.将函数划分为易于理解的子函数,以尽可能保持代码的可读性