Clean Code

整洁代码

  • 代码呈现了需求的细节。将需求明确到机器可以执行的细节程度,就是编程要做的事。而这种规约正是代码。
  • 勒布朗(LeBlanc)法则:Later equals never(稍后等于永不).
  • 糟糕的代码引发混乱!别人修改糟糕的代码时,往往会越改越乱。
  • 整洁的代码只做好一件事。
  • 如果同一段代码反复出现,就表示某种想法未在代码中得到良好的体现。
  • 如果对象功能太多,最好是切分为两个或多个对象。如果方法功能太多,那就使用抽取手段重构之,从而得到一个能较为清晰的说明自身功能的方法,以及另外数个说明如何实现这些功能的方法。
  • 整洁代码的方法:减少重复代码,提高表达力,提早构建简单抽象。
  • 破窗理论:环境中的不良现象如果被放任存在,就会诱使人们仿效,甚至变本加厉。以一幢有少许破窗的建筑为例,如果那些窗不被修理好,可能将会有破坏者破坏更多的窗户。最终他们甚至会闯入建筑内,如果发现无人居住,也许就在那里占领、定居或者纵火。
  • 如果每次签入时,代码都比签出时干净,那么代码就不会腐坏。童子军军规:让营地比你来时更干净。

有意义的命名

  • 名副其实:变量、函数或类的命名应该已经答复了所有的大问题,它该告诉你,它为什么会存在,它做什么事,应该怎么用。如果名称需要注释来补充,那就不算是名副其实。
  • 避免误导:避免使用与本意相悖的词。提防使用不同之处较小的名称。
  • 做有意义的区分:废话都是冗杂。只是为满足编译器或解释器的需要而写代码,就会制造麻烦。
  • 使用读得出来的名称:不要命名自造词。如果名称读不出来的话,讨论的时候就会出现阻碍。
  • 使用可搜索的名称:长名称胜于短名称,搜得到的名称胜于用自造编码代写就的名称。名称长短应与其作用域大小相对应。
  • 避免使用编码:编码已经太多,无谓再自找麻烦,带编码的名称通常不便发音,而且容易打错。
  • 避免思维映射:不应当让读者在脑中把你的名称翻译为他们熟知的名称。明确才是王道。
  • 类名:类名和对象名应该是名词或名词短语,类名不应当是动词。
  • 方法名:方法名应当是动词或动词短语。属性访问器、修改器和断言应该根据其值命名,并依Javabean加上get、set、is前缀。
  • 别扮可爱:如果名称太耍宝,那就只有同作者一般有幽默感的人才能记得住。宁可明确,毋为好玩。言到意到,意到言到。
  • 每个概念对应一个词:给每个抽象概念选一个词,并且一以贯之。
  • 别用双关语:避免将同一单词用于不同目的,同一术语用于不同概念。
  • 使用解决方案领域名称:依据问题所涉领域来命名不是聪明的做法。
  • 使用源自所涉问题领域的名称:如果不能用程序员熟悉的术语来给手头的工作命名,就采用从所涉问题领域而来的名称。
  • 添加有意义的语境:需要用有良好命名的类、函数或名称空间来放置名称,给读者提供语境。如果没这么做,给名称添加前缀是最后一招。可以添加前缀addrFirstName、addrLastName、addrState等,以此提供语境。至少,读者会明白这些变量是某个更大结构的一部分。当然,更好的方案是创建名为Address的类。这样,即便是编译器也会知道这些变量隶属于某个更大的概念了。
  • 不要添加没用的语境:只要短名称足够清楚,就要比长名称好。别给名称添加不必要的语境。

函数

  • 短小:if、else、while语句等,其中的代码块应该只有一行。该行大抵应该是一个函数调用语句。这样不但能保持函数短小,而且,因为块内调用的函数拥有较具说明性的名称,从而增加了文档上的价值。函数不应该大到足以容纳嵌套结构。
  • 只做一件事:要判断函数是否不止只做了一件事,还有一个办法,就是看是否能再拆出一个函数,该函数不仅只是单纯地诠释其实现。
  • 每个函数一个抽象层级:要确保函数只做一件事,函数中的语句都要在同一抽象层级上。程序就像是一系列TO起头的段落,每一段都描述当前抽象层级,并引用位于下一抽象层级的后续TO起头段落。让代码读起来像是一系列自顶向下的TO起头段落是保持抽象层级协调一致的有效技巧。
  • 使用描述性的名称:长而具有描述性的名称,要比短而令人费解的名称好,比描述性的长注释好。
  • 函数参数:最理想的参数数量是零,即无参,然后是一、二,最好不用三个参。如果函数看来需要两个、三个或更多参数,就说明其中一些参数应该封装为类了。对于一元函数,函数和参数应当形成一种良好的动词/名词对形式。
  • 无副作用:参数多数会被自然而然地看作是函数的输入。普遍而言,应避免使用输出函数。如果函数必须要修改某种状态,就修改所属对象的状态吧。
  • 分隔指令与询问
  • 使用异常替代返回错误码:如果使用异常替代返回错误码,错误处理代码就能从主路径代码中分离出来。try…catch代码块搞乱了代码结构,把错误处理与正常流程混为一谈,最好把try和catch代码块的主体部分抽离出来,另外形成函数。error.java依赖磁铁,当error枚举修改时,所有这些其他的类都需要重新编译和部署,使用异常代替错误码,新异常就可以从异常类派生出来,无需重新编译和重新部署。
  • 别重复自己:重复会使代码显得臃肿,而且不易改动。重复可能是所有软件中一切邪恶的根源,许多原则与实践规则都是为控制与消除重复而创建。
  • 结构化编程:只要函数保持短小,偶尔出现的return、break或continue语句没有坏处,甚至还比单入单出原则更具有表达力。另外,goto只在大函数中才有道理,所以应该尽量避免使用。

注释

  • 注释的恰当用法是弥补我们在用代码表达意图时遭遇的失败。每次用代码表达,都该夸奖自己一下。每次写注释,都该做个鬼脸,感受自己在表达能力上的失败。真是只在一处地方有:代码。只有代码能忠实地告诉你它所做的事。那是唯一真正准确的信息来源。
  • 注释不能美化糟糕的代码:与其花时间编写解释你搞出的糟糕的代码的注释,不如花时间清洁那堆糟糕的代码。
  • 好注释:有些注释是必须的,也是有利的。1) 法律信息:公司代码规范要求编写与法律有关的注释。这类注释不应是合同或法典。只要有可能,就指向一份标准许可或其他外部文档,而不要把所有条款放到注释中。 2)提供信息的注释:若注释用来提供基本信息也是好的。 3) 对意图的解释:注释不仅提供了有关实现的有用信息,而且还提供了某个决定后面的意图。 4) 阐释:注释把某些晦涩难明的参数或返回值的意义翻译为某种可读形式。通常,更好的办法是尽量让参数或返回值自身就足够清楚;但如果参数或返回值是某个标准库的一部分,或是你不能修改代码,帮助阐释其含义的代码就会有用。写这类注释回冒着阐述性注释本身就不正确的风险。 5) 警示:用于警告其他程序员会出现某种后果的注释也是有用的。 6) TODO注释:TODO是一种程序员认为应该做,但由于某些原因目前还没做的工作。无论TODO的目的如何,它都不是在系统中留下糟糕代码的借口。你不会愿意代码因为TODO的存在而变成一堆垃圾,所以要定期查看,删除不再需要的。 7)放大:注释可以用来放大某种看来不合理之物的重要性。 8) 公共API中的Javadoc:没有什么比被良好描述的公共API更有用和令人满意的了,标准java库中的Javadoc就是一例。但是,就像其它注释一样,Javadoc也可能误导、不适用或者提供错误信息。
  • 坏注释:坏注释都是糟糕的代码的支撑或借口,或者对错误决策的修正。 1) 喃喃自语:如果只是因为你觉得或者因为过程需要就添加注释,那就是无畏之举。如果你决定写注释,就要花必要的时间确保写出最好的注释。我们唯有检视系统其他部分的代码,弄清事情原委。任何迫使读者查看其他模块的注释都没能与读者沟通好,不值所费。 2) 多余的注释:多余的代码并不能比代码本身提供更多的信息。它没有证明代码的意义,也没有给出代码的意图和逻辑。读它并不比读代码更容易。 3) 误导性注释:不够精确的注释会误导读者。 4) 循规式注释:所谓每个函数都要有Javadoc或每个变量都要有注释的规矩全然是愚蠢可笑的。这类注释突然让代码变得散乱,满口胡言,令人迷惑不解。 5) 日志式注释:有人会在每次编辑代码时,在模块开始处添加一条注释,这类注释就像是一种记录每次修改的日志。这种冗杂的记录只会让模块变得凌乱不堪,应当全部删除。 5) 废话注释:对于显然之事喋喋不休,毫无新意。 6) 能用函数或变量时就别用注释。 7) 位置标记:如果标记栏不多,就会显而易见。所以,尽量少用标记栏,只在特别有价值的时候用。 8) 括号后面的注释:这对于含有深度嵌套结构的长函数可能有意义,但只会给我们更愿意编写的短小、封装的函数带来混乱。如果你发现自己想标记右括号,其实应该做的是缩短函数。 9) 归属与署名 10) 注释掉的代码:直接把代码注释掉是讨厌的做法。不用的代码直接删掉即可。 11) HTML注释:如果注释将由某种工具(例如Javadoc)抽取出来,呈现到网页,那么该是工具而非程序员来负责给注释加上合适的HTML标签。 12) 非本地信息:假如你一定要写注释,请确保他描述了离他最近的代码。别在本地注释的上下文环境中给出系统级的信息。 13) 信息过多:别在注释中添加有趣的历史性话题或者无关的细节描述。 14) 不明显的联系:注释及其描述的代码之间的联系应该显而易见,如果你不嫌麻烦要写注释,至少让读者能看着注释和代码,并且理解注释所谈何物。注释的作用是解释未能自行解释的代码,如果注释本身还需要解释,就太遗憾了。 15) 函数头:短函数不需要太多描述,为只做一件事的短函数选个好名字,通常要比写函数头注释要好。 16) 非公共代码中的Javadoc:javadoc对于公共的API非常有用,但对于不打算成为公共用途的代码就令人厌恶。

格式

  • 格式的目的:或许你认为“让代码能工作”是专业开发者的头等大事,然而,沟通才是,而代码格式就关乎沟通。
  • 垂直格式:短文件比长文件易于理解。 1) 向报纸学习:名称应当简单且一目了然,名称本身应该足够告诉我们是否在正确的模块中。源文件最顶部应该给出高层次概念和算法。细节应该往下渐次展开,直至找到源文件中最底层的函数和细节。 2) 概念间垂直方向上的区隔:几乎所有的代码都是从上往下读,从左往右读。每行展现一个表达式或一个子句,每组代码行展示一条完整的思路。这些思路用空白行区隔开来。 3) 垂直方向上的靠近:如果说空白行隔开了概念,靠近的代码行则暗示了他们之间的紧密关系。 4) 垂直距离:变量声明应尽可能靠近其使用位置。实体变量应该在类的顶部声明。相关函数,若某个函数调用了另外一个,就应该把他们放到一起,而且调用者应该尽可能放在被调用者上面。概念相关,概念相关的代码应该放到一起,相关性越强,彼此之间的距离就该越短。
  • 横向格式:一行代码最好不要超过120个字符。 1) 水平方向上的区隔和靠近:赋值操作符的周围加上空格,以此达到强调的目的。函数名和左圆括号之间不加空格,这是因为函数与其参数密切相关,如果隔开,就会显得互无关系。函数调用括号中的参数用空格隔开。乘法因子之间不加空格,因为他们具有较高的优先级,加减法运算项之间用空格隔开,因为加减法优先级较低。 2) 水平对齐:这种对齐没什么用,像是在强调不重要的东西,把读者的眼光从真正的意义上拉开。如果有较长的列表需要做对齐处理,那问题就是在列表的长度上而不是对齐上。 3) 缩进:源文件是一种继承结构,而不是一种大纲结构。这种继承结构中的每一层级都圈出了一个范围,名称可以在其中声明,而声明和执行语句也可以在其中解释。要让这种范围式继承结构可见,我们依源代码行在继承结构中的位置对源代码行做缩进处理。 4) 空范围:有时,while和for语句的语句体为空,但也要确保范围体的缩进。
  • 团队规则:每个程序员都有自己喜欢的格式规则,但如果在一个团队中工作,就是团队说了算。一组开发者应当认同一种格式风格,每个成员都应该采用那种风格。

对象和数据结构

  • 数据抽象:隐藏实现并非只是在变量之间放上一个函数层那么简单。隐藏实现关乎抽象!类并不简单地用取值器和赋值器将其变量推向外间,而是曝露抽象接口,以便用户无需了解数据的实现就能操作数据本体。我们不愿曝露数据细节,更愿意以抽象形态表述数据。
  • 数据、对象的反对称性:1) 过程式代码(使用数据结构的代码)便于在不改动既有数据结构的前提下添加新函数。面向对象代码便于在不改动既有函数的前提下添加新类。 2) 在任何一个复杂系统中,都会有需要添加新数据类型而不是新函数的时候。这时,对象和面向对象就比较适合。另一方面,也会有想要添加新函数而不是数据类型的时候。在这种情况下,过程式代码和数据结构更合适。 3) 一切都是对象只是一个传说,有时候你真的想要在简单数据结构上做一些过程式的操作。 4) 对象与数据结构之间的差异。对象把数据隐藏于抽象之后,曝露操作数据的函数。数据结构曝露其数据,没有提供有意义的函数。5) 过程式形状代码:
    20170905221832670.jpg
    多态式形状:
    20170905221833078.jpg
  • 得墨忒耳律:模块不应了解它所操作对象的内部情形。更准确的说,得墨忒耳律认为,类C的方法f只应该调用以下对象的方法:a. 类C b. 由f创建的对象 c. 作为参数传递给f的对象 d. 由C的实体变量持有的对象。方法不应调用由任何函数返回的对象的方法,换句话说就是,只跟朋友谈话,不与陌生人谈话。 1) 火车失事:这类代码常被称作火车失事,因为他看起来就像是一列火车。 2) 混杂:这种混淆有时会不幸导致混合结构,一半是对象,一半是数据结构。这种结构拥有执行操作的函数,也有公共变量或公共访问器及改值器。 3) 隐藏结构
  • 数据传送对象:最为精炼的数据结构,是一个只有公共变量、没有函数的类。这种数据结构有时候被称为数据传送对象,或DTO(Data Transfer Objects)。DTO是非常有用的结构,尤其是在与数据库通信、或解析套接字传递的消息之类场景中。

错误处理

  • 错误处理很重要,但如果他搞乱了代码逻辑,就是错误的做法。
  • 使用异常而非返回码:设置一个错误标识,或者返回给调用者检查的错误码,它们搞乱了调用者代码。调用者必须在调用之后即刻检查错误。不幸的是,这个步骤很容易被遗忘。所以,遇到问题时,最好抛出一个异常。调用代码很整洁,其逻辑不会被错误处理搞乱。
  • 先写try-catch-finally语句:异常的妙处之一是,它们在程序中定义了一个范围。
  • 使用不可控异常:可控异常的代价就是违反开放/闭合原则。如果你在方法中抛出可控异常,而catch语句在三个层级之上,你就得在catch语句和抛出异常处之间的每个方法签名中声明该异常。
  • 给出异常发生的环境说明:你抛出的每个异常,都应当提供足够的环境说明,以便判断错误的来源和处所。在java中,你可以从任何异常里得到堆栈踪迹(stack trace);然而,堆栈踪迹却无法告诉你该失败操作的初衷。应创建信息充分的错误信息,并和异常一起传递出去。在消息中,包括失败的操作和失败类型。
  • 依调用者需要定义异常类:当我们在应用程序中定义异常类时,最重要的考虑应该是它们如何被捕获。对于代码的某个特定区域,单一异常类通常可行。伴随异常发送出来的信息能够区分不同错误。如果你想要捕获某个异常,并且放过其他异常,就使用不同的异常类。
  • 定义常规流程:创建一个类或配置一个对象,用来处理特例。你来处理特例,客户代码就不用应付异常行为了。异常行为被封装到特例对象中。
  • 别返回null值:返回null值,基本上是在给自己增加工作量,也是在给调用者添乱。只要有一处没检查null值,应用程序就会失控。如果你打算在方法中返回null值,不如抛出异常,或是返回特例对象。如果你在调用某个第三方API中可能返回null值的方法,可以考虑用新方法打包这个方法,在新方法中抛出异常或返回特例对象。
  • 别传递null值:在方法中返回null值是糟糕的做法,但将null值传递给其他方法就更糟糕了。除非API要求你向他传递null值,否则就要尽可能避免传递null值。

边界

单元测试

  • TDD三定律:1) You must write a failing unit test before you write production code. ——编写生产代码前先写单元测试 2) You must stop writing that unit test as soon as it fails; and not compiling is failing. ——测试一旦失败,开始写生产代码 3) You must stop writing production code as soon as the currently failing test passes. ——老测试一旦通过,返回写新测试。
  • 保持测试整洁:测试代码和生产代码一样重要。他可不是二等公民,他需要被思考,被设计和被照料,他该像生产代码一般保持整洁。单元测试让你的代码可扩展、可维护、可复用。有了测试,你就不担心对代码的修改,没有测试,每次修改都可能带来缺陷。测试越脏,代码就会变得越脏,最终,你丢了测试,代码开始腐坏。总而言之,因为测试,使改动变的可能。
  • 整洁的测试:测试如何才能做到可读?和其他代码一样:明确、简洁、还有足够的表达力。 1) 面向特定领域的测试语言 2) 双重标准
  • 每个测试一个断言:JUnit中每个测试函数都应该有且只有一个断言语句。最好的说法是单个测试中的断言数量应该最小化。最佳规则也许是应该尽可能减少每个概念的断言数量,每个测试函数只测试一个概念。
  • F.I.R.S.T.:整洁的代码应该遵循5条规则 1) 快速(Fast):测试应该能快速运行,否则你就不会想使用它 2) 独立(Independent):测试应该相互独立,某个测试不应为下一个测试设定条件。 3) 可重复(Repeatable):测试应当可在任何环境中重复通过。 4) 自足验证(Self-Validating):测试应该有布尔值输出。无论是通过还是失败,你不应该查看日志文件来确认测试是否通过。 5) 及时(Timely):单元测试应恰好在使其通过的生产代码之前编写。如果在编写代码之后,你会发现生产代码难以测试。

  • 类的组织:遵循标准的java约定,类应该从一组变量列表开始。显示公共静态常量,然后是私有静态变量,以及私有实体变量,很少会有公共变量。公共函数应跟在变量列表之后。我们喜欢把由某个公共函数调用的私有工具函数紧随在该公共函数后面。我们喜欢保持变量和工具函数的私有性,但有时,我们也需要用到受护变量或工具函数,好让测试可以访问到。
  • 类应该短小:对于函数,我们通过计算代码行数衡量大小。对于类,我们通过计算权责来衡量。类的名称应当描述其权责,实际上,命名正是帮助判断类的长度的第一个手段。类名越含混,该类越可能拥有过多权责。 1) 单一权责原则(SRP):类或模块应有且只有一条加以修改的理由。分而治之,其在编程行为中的重要程度等同于在程序中的重要程度。在强调一下,系统应该由许多短小的类而不是少量巨大的类组成。每个小类封装一个权责,只有一个修改的原因,并与少数其他类一起协同达成期望的系统行为。 2) 内聚:类中的每个方法都应该操作一个或多个这种变量。如果一个类中的每个变量都被每个方法所使用,则该类具有最大的内聚性。 3) 保持内聚性就会得到许多短小的类:如果有些函数想要共享某些变量,为什么不让他们拥有自己的类呢?当类丧失了内聚性,就拆分他!
  • 为了修改而组织:每处修改都让我们冒着系统其他部分不能如期望般工作的风险。在整洁的系统中,我们对类加以组织,以降低修改的风险。我们希望将系统打造成在添加或修改特性时尽可能少惹麻烦的架子。在理想系统中,我们通过扩展系统而非修改现有代码来添加新特性。隔离修改:具体类包含实现细节(代码),而抽象类则只呈现概念。依赖于具体细节的客户类,当细节改变时,就会有风险。我们可以借助接口和抽象类来隔离这些细节带来的影响。

系统

  • 将系统的构造与使用分开:首先,构造和使用是非常不一样的过程。软件系统应将启始过程和启始过程之后的运行时逻辑分开,在启始过程中构建应用对象,也会存在互相缠结的依赖关系。 1) 分解main:将全部构造过程搬迁到main或被称之为main的模块中。 2) 工厂:有时应用程序也要负责确定何时创建对象,这种情况下,我们可以使用抽象工厂模式。 3) 依赖注入:依赖注入可以实现分离构造和使用,控制反转在依赖管理中的一种应用手段。
  • 扩容:“一开始就做对系统”纯属神话。反之,我们应该只去实现今天的用户故事,然后重构,明天再扩展系统、实现新的用户故事。这就是迭代和增量敏捷的精髓所在。
  • Java代理:
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,495评论 18 139
  • 前言 最近在团队推行Code Review,遇到一个头痛的问题。当向伙伴的代码提一个comment时,他们不解为什...
    CatchZeng阅读 5,149评论 1 19
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,050评论 25 707
  • 无常是为了什么存在 有一天你突然发现 时间不再有长短 那时的你 会不会突然想起 这时的我 也许 我真的会死掉 在某...
    五色浮元子_阅读 163评论 0 0
  • 日子过得越发无聊,回过头我才发现自己还是想好好画画的
    嘿小明儿阅读 163评论 0 5