观看Clean Code(整洁代码)记录-1

一、前言

规则重要性

“写代码是非常酷,非常重视的事情,我们做的每一步都可能影响到世界的每一个地方!”
“你的代码正在控制整个世界,甚至生命!”
“任何事物都会建立规则来约束另一方,而编程也需要建立一套标准来使程序员遵循它!”

我们为什么这么慢

在项目开始的初期,获取一个、两个功能会非常的快:

编码的灵感会从指尖迸发出来,无数的想法都会涌现!
同事:你的速度怎么这么快!你真厉害!

一年后,再次让这个项目组进入并修改功能时:
抱歉,修改这个功能预计时间是6个月~
啊,为什么呢,之前不是很快吗!
抱歉,我们很担心,如果我们触及哪怕一行代码,所有的地方都会有问题啊!!!
...

当我们前期追求速度时,如果没有规范,则会越来越混乱,这个过程可能很长。但是到了后面则会出现无法交付的情况,因为你想要修改某些功能时哪怕是1%也需要消耗数倍的开发时间。但是我们都知道项目经理在外的宣称完成时间肯定是更短,6个月是不可能宣称的时间。
增加一倍的工作人员?当你增加新人时,只会在前期的工程时间延长。因为他们需要融入这个项目。
给他们培训?那谁来给他们培训呢,首先是制造这场混乱的人,这个说法不准确,培养新人的不是旧人,而是旧代码。当新人进入这个火坑中,为了能够迅速找到问题,他们会去翻阅旧代码,了解之后就会效仿这些旧代码,让这个火坑继续恶化,混乱。

如果代码得以整洁有序,至少不会乱成一锅粥。而这一基础就需要我们走的慢一点,稳一点;快会让思维变得混乱,因为我们需要尽快完成任务。

当我们在debug的时候,测试,debug在断点中跳跃,突然成功了,好了任务完成!
...

但是这项工作只完成了一半,此时这项任务只是被胶带和铁丝捆绑后的半成品,你需要将它整理一下。

什么是代码整洁

“我喜欢我的代码是优雅和高效的,整洁就意味着只做一件事”
“整洁的代码是简单和直接的,读起来像写的很好的散文”
“整洁的代码看起来总是由一个关心的人写的,这是一个多么可爱的声明”

代码完成功能只是一半,能让他人看懂的代码更重要。

整洁的代码是在你看代码是,他的每一个功能都是没有惊喜的,都是你意料之中的。

二、代码对比效果

代码一:

public static String testableHtml (PageData pageData, boolean includeSuiteSetup) throws Exception {
WikiPage wikiPage = pageData.getWikiPage();
StringBuffer buffer = new StringBuffer();
if (pageData.hasAttribute("Test")) {
if (includeSuiteSetup) {
WikiPage suiteSetup =
PageCrawlerImpl.getInheritedPage(
SuiteResponder.SUITE_SETUP_NAME, wikiPage
);
if (suiteSetup != null) {
WikiPagePath pagePath =
suiteSetup.getPageCrawler().getFullPath(suiteSetup);
String pagePathName = PathParser.render(pagePath);
buffer.append("!include -setup .")
.append(pagePathName)
.append("\n");
}
}
WikiPage setup =
PageCrawlerImpl.getInheritedPage("SetUp", wikiPage);
if (setup != null) {
WikiPagePath setupPath =
wikiPage.getPageCrawler().getFullPath(setup);
String setupPathName = PathParser.render(setupPath);
buffer.append("!include -setup .")
.append(setupPathName)
.append("\n");
}
}
buffer.append(pageData.getContent());
if (pageData.hasAttribute("Test")) {
WikiPage teardown =
PageCrawlerImpl.getInheritedPage("TearDown", wikiPage);
if (teardown != null) {
WikiPagePath tearDownPath =
wikiPage.getPageCrawler().getFullPath(teardown);
String tearDownPathName = PathParser.render(tearDownPath);
buffer.append("\n")
.append("!include -teardown .")
.append(tearDownPathName)
.append("\n");
}
if (includeSuiteSetup) {
WikiPage suiteTeardownp =
PageCrawlerImpl.getInheritedPage(
SuiteResponder.SUITE_TEARDOWN_NAME, wikiPage
);
if (suitTeardown != null) {
WikiPagePath pagePath =
wikiPage.getPageCrawler().getFullPath(suiteTeardown);
String pagePathName = PathParser.render(pagePath);
buffer.append("!include -teardown .")
.append(pagePathName)
.append("\n");
}
}
}
pageData.setContent(buffer.toString());
return pageData.getHtml();
}

在抽象区和抽象区之间不建议跨越过大。因为在编码过程中思维的如此上下跨越,但是如此却让阅读思维混乱。

代码二:

public static String renderPageWithSetupAndTeardowns (PageData pageData, boolean isSuite) throws Exception {
boolean isTestPage = pageData.hasAttribute("Test");
if (isTestPage) {
WikiPage testPage = pageData.getWikiPage();
StringBuffer newPageContent = new StringBuffer();
includeSetupPages(testPage, newPageContent, isSuite);
newPageContent.append(pageData.getContent());
includeTeardownPages(testPage, newPageContent, isSuite);
pageData.setContent(newPageContent.toString());
}
return pageData.getHtml();
}

代码三:

public static String renderPageWithSetupAndTeardowns (PageData pageData, boolean isSuite) throws Exception {
if (isTestPage(pageData))
includeSetupAndTeardownPages(pageData, isSuite);
return pageData.getHtml();
}

虽然看不到其他细节部分,但是他给予了我们一个功能的大纲或者介绍,告诉我们他的大致功能可能是什么,细节则在被封装的函数中。两者的区别体现了抽象层的高低。
函数的规模应该限定为只做一件事情,这是因为你可以有意义的提取一件事情,但是在被无数次提取函数所形成函数海洋后,我们并不会被他所淹没,因为这是函数名称和对象需要区分的事情。
github
上面的代码将近3000行,并且他都是为了完成一个事情,虽然这方面有点主观意愿,但是它确实是只完成一件事情。
github
此时,将他们提取成Class,类,貌似更合理。

三、简单的规则介绍

在函数构建过程中,函数名的规则非常重要,函数的参数最好不要超过三个,如果参数非常具有凝聚力以至于他们必须用到,那就封装成类。在传参类型中,不建议使用boolean类型,因为在使用boolean类型后,你一定会在函数中使用这个boolean类型来做判断,即if,既然如此,为什么不建立两个函数,而这个boolean类型的抽象层就在调用函数那边来处理。

尽量避免Switch语句:

  1. 当我们针对switch的判断类型增加一个新类型对象时,我们必须要翻阅整个代码,将所有的switch补齐。
  2. 当我们针对switch代码修改时,需要编译一整套代码。(jar文件,一个运行时的链接加载器)

多态是解决多类型处理的方案。(开闭原则:一个系统,一个模块应对扩展开放,但对修改关闭,你应该能够扩展一个模块的行为,而不需要修改该模块。)

副作用:副作用的经典定义是对系统状态的改变,如果调用了一个函数,而该函数对系统状态发生变化,那么这个函数有一个副作用。例如打开文件的open函数使系统给予了一个可配置内存块。
副作用一般是成对出现,开和关,获取和释放。让我们无需管理他的技术是垃圾回收,但是他是一根拐杖,很多不合理方面,因为我们没有以一种合理的方式编写代码。但是他依旧给了我们更安全的编码体验。
副作用函数会给我们直接的体验就是,某些时候两个函数可能因为某些原因顺序必须保持如此否则就会内存泄漏
建议在使用副作用函数时返回void,而无副作用函数则使用带返回值,这样可以让我们更轻易理解。

使用一个异常比返回一个错误代码更好(java带异常处理),try代码块只做一件事情,就是验证函数是否会有异常问题,不建议在try代码块中添加其他的代码,这只会增加阅读时的理解时间。

尽量避免重复代码。

结构化编程,每一种算法都可以由三种结构组成:序列、判断和迭代。

测试代码的重要性。

注释:解释代码。注释是在名称限制时会使用的解释方案,但如今丰富的命名方案,可以完成代码自解释。
当我们完成第一次注释后,可能再也不会去维护这个注释,一方面注释可以帮我们理解代码,但是相反的,注释可能会让我们误解代码。注释的正确使用是为了弥补我们在代码中的表达失败,在我们可以用代码表达的范围中,我们不需要注释。
但是我们很多时候确实无法在代码中表达作者的思路和想法,因为未能很好的使用代码表达自己,是一种失败。
大多数人并不会写错误的注释,但是注释会随着时间的推移而退化。因此更希望我们的注释代码颜色,更贴近ide背景色,这样他们更具有忽略性,请多注视代码,如果无法理解,再看看我们。注释无法遮挡失败的代码。
对比代码:

// check to see if the employee is eligible for full benefits
if (((employee.flags & HOURLY_FLAG) > 0) && (employee.age > 65)) {
// ...
}


if (employee.isEligibleForFullBenefits()) {
// ...
}

另外在使用TODO注释时也需要注意,同注释一样,他更想表明一点,此处功能的不同处,但是也会出现上述特点,并加重其味道。

不要注释代码,并将它发不到生产环境中。

JavaDoc在对外提供接口是非常不错的选择,但是如果只是内部开发,则无任何意义。

命名规范:
变量名称应该与包含他的作用域的大小成正比。
函数名称应该与包含他的作用域的大小成反比。
类名的大小与包含他的作用域大小成反比。

public List<int[]> getThem() {
List<int[]> list1 = new ArrayList<>();
for (int[] x : gameBoard)
if (cell[0] == 4)
list1.add(cell);
return list1;
}

对比
public List<int[]> getFlaggedCells() {
List<int[]> flaggedCells = new ArrayList<>();
for (int[] cell : gameBoard)
if (cell[STATUS_VALUE] == FLAGGED)
flaggedCells.add(cell);
return flaggedCells;
}

在命名时很容易出现非常大的歧义:

Account getActiveAccount();
List<Account> getActiveAccounts();
List<Account> getActiveAccountInfo();

四、我就是你的新CTO

我希望你在发布代码时,你知道他的作用,你会在能力范围内确定他是有效的,而不是靠猜。

  • 具有稳定的生产力。
  • 让系统廉价的适应性。
  • 持续改进
  • 无畏的能力
  • 最高质量
  • 让测试一无所获
  • 自动化测试
  • 团队中人员互相引领(其中正确的结对编程,可以有效的减少信息孤岛的形成)
  • 诚实的估计(不要吸收预估风险)
  • 学会说”不”
  • 测试驱动开发
    1. 在你完成一个失败的测试前,你不允许写任何生产代码
    2. 你不允许写出更多的足以失败(无法编译)的测试内容
    3. 你不允许再编写任何足以通过当前失败测试的生产代码

五、建筑,架构

敏捷不是没有架构,敏捷是一个框架,我们可以在其中应用一个好的架构。
当我开始写每一行代码的时候,当我开始组装该系统中的模块的时候,会发现有一些压力。这些模块和代码行所创造的,影响了这个建筑,导致它改变了形状,而这种建筑形状的变化仍在继续,每当有人添加新的功能或行为时,为改变一个功能或在系统中增加一个新的设施,给架构带来压力时,架构必须做出反应。
因此,建筑是一个活生生的东西。它随着系统每天都在变化,他不是一个固定的地图,在未来的几年中都要遵循。这将是我们一直必须调整、调整、调整和回应的事情。在任何软件项目的整个生命周期中。

建筑的规则是独立于所有其他变量,软件结构的规则是相同的。软件架构的规则保持不变,让程序员会进入那个陷阱,新的肯定是好的,对此一定要小心,因为没有多少新的东西。

构建软件在成功后,会有一种魔力,这种魔力就是把建造他的努力和维护他的努力降到最低。继续增加的斗争被最小化了,软件的功能和灵活性得到最大限度的发挥。

软件架构的实际目标是为了尽量减少建立和维护软件系统所需要的人力资源。

衡量设计质量的标准可以通过衡量为满足客户需求所付出的努力来衡量。如果努力保持不变或随着时间的推移而缩减,那么这个设计是好的。 但如果维护一个系统所需要的努力随着时间的推移而增加,那么设计和架构都是坏的。

走的快的秘诀是不要建造会使你走的慢的路障。那么首先需要对代码去除不好的代码。

软件的两个价值:

  1. 需求
  2. 结构

软件系统中的利益相关者认为,需求和变化只因范围而复杂。但是如果是小范围的变动,将通过软件的结构引起巨大的反响。
但是所有的利益相关者,只看重需求。他们并不看重第二价值,因此在排序时他永远是最后考虑的。因此,我们必须传达第二个价值观的重要性。需要告诉他们真相。

六、交互

交互器倾向于在没有电脑等设备的情况下,也可以完成流程。
用例更倾向于功能细节及应用。

mvc是第一个被命名的设计模式。他更适用于一个小件的控制,比如一个按钮,针对他的调用进行一次数据流转。但是现在的大型项目,往往都是在页面将所有数据传给控制器,而控制器的工作是解析所有的输入数据,然后在一边与业务对象交谈,业务对象开始进行运行,然后控制器将控制权移交给视图,而视图从业务对象中收集数据并显示出来。
而这里的问题是没有硬性的界限,控制器对业务对象有密切的了解,发生的情况是,很多时候业务对象开始积累类似控制器的功能;或者更糟糕的是,控制器开始积累类似业务规则的功能。同时的事情也发生在view上,view往往会积累业务。因为这里的边界线有问题。
因此,虽然mvc是一件很好的事情,但这是他在小范围内完成时,而当他在大范围内完成时,他开始动摇。
因此边界对我们来说将变得非常重要,因为边界是建筑的本质,那是一个建筑的边界,并注意到在建筑边界的对面,所有的依赖性都指向一个方向,而这个方向是朝着业务规则的方向发展。这是所谓的跨越架构边界的依赖性规则。对业务规则的所有依赖性。
github

数据库是一个io设备,从程序员角度看,数据库就是保护数据的。
我们不希望业务规则知道数据库,我们不希望商业规则知道续集,我们不希望业务规则知道数据库的模式。我们只希望数据库低于建筑边界。
github
orm对象关系型映射器,数据库表并不是直接映射到数据库关联的对象。

一个好的架构,允许尽可能长时间的推迟关键决策,将他们推迟到你有足够信息来实际制作或不制作他们的地方。大致为允许推迟决策,推迟到最后一个负责的时刻。
在使用一个新的框架是,我们必须为他们提供环境,而非是他们给我们提供环境,只提供功能。这是一个不对称性,但是也是必须面对的问题。

七、敏捷

如何管理软件项目

失败的管理会导致各种问题,无效工作量、项目延期、等等。
因此项目管理者需要确定:

  • 多好
  • 多快
  • 多便宜
  • 怎么做

一个好的管理者,会通这些选项的进行抉择(舍弃一部分)来对项目进行管理。

在对这些进行管理时,作为管理者,最需要的就是信息、数据。管理者需要知道发生了什么,他们需要获取数据。
比如,根据工作量,设定分数值,然后将这些功能完成后的分值进行统计,得到项目实施情况,

敏捷过程并不会让我们更快、也不会让我们变得很慢,敏捷知识一种产生数据的方法,这样就知道事情有多糟糕。然后,就可以进行管理。

开发环境:

  • 首先我们需要知道的是截止时间。当然,设置虚假的截止是不道德的。
  • 然后是需求,如果一成不变当然最好。但是到多数顾客并不知道他们想要什么,但是当他们看到一些事情发生时,发现这些并不是他们想要的,此时需求改变了。因此我们需要在客户面前把事情说清楚,

整理需求往往没有一个标准答案,因此往往会导致需求不明确。
设计阶段也同样,他们并不是敷衍了事,因为他们并没有完成和未完成两个状态,非常的开放。有点像通货膨胀。

此时,敏捷来解决这些问题。
切割时间点,根据需求迅速进入设计阶段(不编码)已能够搭建需求框架及系统框架。这种方式会以非常小的需求嵌入到系统中。
当进入第二个节点时,就可以进行编码,选择较稳定的需求点进行开发。

回到管理者需要确定的问题:

  • 多好
  • 多快
  • 多便宜
  • 怎么做

增加人手,这并不会迅速加快需求吞吐量,反而会减慢速度,因为这些新人会压榨老人的时间,甚至能力问题导致延误。
降低质量,为了时间,减少review、减少测试等等,这都可以让系统速度变快。但是一旦如此,这会导致系统的问题大范围增大,而不是某一个功能出现问题。因此,如果想要做的快,必须做得好,这个标准不能降低。
考虑将非硬性需求去除到验收节点中,这中间就需要争论与会议,但是如果你有上面的数据,那么就可以掌握优势。

     -------------------------------完整团队------------------------------------
--------------标准编码--------------
------TDD-----
共同所有权 可持续的速度(40h)
客户测试 简单设计 重构 计划游戏
持续集成 隐喻
---结对编程---
------------------------------------
---------------------------------小型发布----------------------------------