编写干净的代码是每个程序员的追求。《clean code》指出,要想写出好的代码,首先要知道什么是脏代码,什么是干净代码;然后通过大量的刻意练习,你才能真正写出干净的代码。WTF/min是衡量代码质量的唯一标准,而Bob大叔在他的书中称糟糕的代码涉水,这只会凸显我们是糟糕代码的受害者。国内有一个比较合适的词汇:屎山,虽然不是很优雅但是比较客观,程序员既是受害者也是加害者。对于什么是cleancode,书中给出了大师的总结:BjarneStroustrup:Elegantandefficient;测试RonJeffries:无重复,单一职责,表现力(Expressiveness)其中,我最喜欢的是表现力(Expressiveness)的描述。功能,不多也不少。命名的艺术坦率地说,命名是一件很难的事情。想出一个合适的命名需要做很多工作,尤其是当我们的母语不是编程语言中常用的英语时。但这一切都是值得的,好的命名可以让你的代码更直观、更有表现力。一个好的起名应该具备以下特点:名副其实的名字。一个好的变量名会告诉你:它是什么,为什么存在,以及如何使用它。如果需要通过注释来解释变量,那就不是那么名副其实了。以下是书中的示例代码,展示了命名如何提高代码质量#badcodedefgetItem(theList):ret=[]forxintheList:ifx[0]==4:ret.append(x)returnret#goodcodedefgetFlaggedCell(gameBoard):'''扫雷游戏,flagged:Flip'''flaggedCells=[]forcellingameBoard:ifcell.IsFlagged():flaggedCells.append(cell)returnflaggedCells避免误导,不卖羊,不盖地道缩写在这里我要吐槽一下前两段天才看到的一段代码居然用l作为变量名;而且user其实就是一个列表(单复数没学好!!)。代码之间有意义的区分是写给机器执行的和给人看的,所以概念一定要区分一下不能阅读,那么讨论就会像一只傻鸟一样易于使用和搜索名称的长度应与其范围的大小相对应,以避免思维映射。比如你在代码里写了一个temp,每次看到这个词,读者都得翻译成它真正的意思。注释富有表现力的代码不需要注释。正确使用注释是为了弥补我们在代码中无法表达自己的不足。这听起来令人沮丧,但这是事实。其实在代码中,注释只是二手信息,两者不同步或不相等才是注释最大的问题。书中给出了一个很形象的例子来说明:用代码来解释,不是注释bad//checktoseeiftheemployeeiseligibleforfullbenefitif((employee.flags&HOURLY_FLAG)&&(employee.age>65))goodif(employee.isEligibleForFullBenefits())因此,当你想添加注释,你可以考虑是否可以通过修改名称,或者修改函数(代码)的抽象层次来表明代码的意图。当然,你不能因为噎住而放弃进食。书中指出以下几种情况是很好的注解。法律信息是意图的注释。为什么要这样做来警告TODO注解来放大看似不合理的事情的重要性?其中,第二点是我个人最认同的。还有第5点,做什么用命名很容易表达,但是为什么不直观,尤其是涉及到专业知识和算法的时候。另外,一些一开始感觉“不太优雅”的代码可能会有特殊的愿望,所以应该对此类代码进行注释,说明为什么会这样。例如,为了提高关键路径的性能,可能会牺牲一些代码的可靠性。可读性。最糟糕的一种注释是过时的或错误的注释,这对代码的维护者(可能是几个月后的你自己)是一个巨大的伤害。不幸的是,除了代码审查,没有简单易行的方法来确保代码和注释的完整性。同步。一个函数的职责单一一个函数应该只做一件事,而这件事应该通过函数名来明确展示。判断的方法很简单:看功能是否可以拆分成另一个功能。该函数要么执行do_sth,要么查询query_sth。最恶心的是函数名明明只会query_sth,实际上却会do_sth,使得函数产生了副作用。比如书中的例子decrypt(codedPhrase,password);if("ValidPassword".equals(phrase)){Session.initialize();returntrue;}}returnfalse;}}函数抽象层次每个函数都有一个抽象层次,函数中的语句必须在同一个抽象层次,不同的抽象层次不能放在一起。例如,如果我们要将大象放入冰箱,它应该是这样的:defpushElephantIntoRefrige():openRefrige()pushElephant()closeRefrige()函数中的三行代码描述了如何将大象放入冰箱的方法共有三个与进入冰箱顺序相关的步骤。显然,pushElephant步骤可能包含很多子步骤,但是在pushElephantIntoRefrige这一层,不需要了解太多细节。当我们想通过阅读代码来理解一个新项目时,我们一般会采用广度优先的策略,从上到下阅读代码,先了解整体结构,再深入感兴趣的细节。如果实现细节没有很好地抽象(并浓缩成一个名副其实的函数),那么读者很容易迷失在细节的海洋中。这在某种程度上类似于金字塔的原理。每一层都是为了证明上层的观点,也需要下一层的支持;同一级别之间的多个参数需要某种逻辑。关系排序。pushElephantIntoRefrige是中心论点,需要多个子步骤的支持,并且这些子步骤之间有逻辑顺序。函数参数函数参数越多,输入条件组合越多,需要的测试用例越多,越容易出问题。与返回值相比,输出参数比较难理解。我非常同意这一点。输出参数确实不直观。从函数调用者的角度来看,返回值一目了然,但很难识别输出参数。输出参数通常会强制调用者检查函数签名,这确实很不友好。将布尔值(在书中称为标志参数)传递给函数通常不是一个好主意。尤其是当通过True或False后的行为不是一个事物的两个方面,而是两个不同的事物。这显然违反了函数的单一职责约束,解决方法很简单,就是使用两个函数。不要在功能层面重复自己,这是实现复用最简单、最直观的方式。很多IDE也很难帮我们把一段代码重构成一个函数。但是,在实践中,也会出现这样的情况:一段代码用在多个方法中,但并不完全相同。如果抽象成一个通用的函数,那么就需要加参数,加上ifelse来区分。这就有点尴尬了,貌似可以重构,但并不完美。上面的一些问题是因为这段代码也违反了单一职责原则,做了不止一件事,导致难以复用。解决方案是将方法细分,以更好地复用。也可以考虑用模板方法来处理差异。测试很遗憾的是,在我经历过的项目中,测试(尤其是单元测试)并没有得到足够的重视,也没有尝试过TDD。也正是因为缺席,我才感受到一场好考的珍贵。我们常说好的代码需要可读性、可维护性和可扩展性。好的代码和架构需要不断的重构和迭代,而自动化测试是这一切的基础。没有高覆盖率、自动化单元测试、回归测试,没人敢修改代码,只能任其烂掉。即使是为核心模块写了单元测试,一般也很随意,认为只是测试代码,不配生产代码的地位,只要能跑通就行。这导致测试代码的可读性和可维护性非常差,使得测试代码很难随着生产代码更新和演进,最终导致测试代码的失败。所以,肮脏的测试-相当于-没有测试。因此,测试代码的三要素:可读性、可读性、可读性。测试的原则和准则如下:除非是为了让失败的单元测试通过,否则不允许编写任何生产代码。不允许编写比现在更多的单元测试,编译失败就是失败。您不得编写足以通过一个失败的单元测试的生产代码。您不得编写足以通过一个失败的单元测试的生产代码。代码测试的FIRST准则:快速(Fast)测试应该足够快并且尽可能自动化。独立(Independent)测试应该是独立的。不要互相依赖。可重复(Repeatable)测试应该在任何环境中重复进行。自验证测试应该有bool输出。不要通过低效的方式看日志来判断测试是否通过。及时(Timely)测试应该在其相应的生产代码之前及时编写
