代码规范

整洁代码
将需求明确到机器可以执行的细节程度,就是编程要做的事,而这种规约正是代码。
勒布朗法则:稍后等于永不(Later equeals never)。
花时间保持代码整洁不但有关效率,还有关生存。
经理会奋力卫护进度和和需求。开发工程师应该以同等的热情卫护代码。就像医生如果按病人说的办,是一种不专业的态度,那程序员为了进度妥协,也是不专业的做法。
赶上期限的唯一方法就是始终尽可能保持代码整洁。
编写整洁代码的程序员就先是艺术家,他能用一系列变换把一块白板变成由优雅代码构成的系统。

什么是整洁代码:
我喜欢优雅和高效的代码。代码逻辑应当直截了当,叫缺陷难以隐藏;尽量减少依赖关系,使之便于维护;
依据某种分层战略完善错误处理代码;行能调至最优,省得引诱别人做没规矩的优化,搞出一堆混乱来。整洁的代码只做好一件事。---Bjarne Stroustrup
整洁的代码简单直接。整洁的代码如同优美的散文。整洁的代码从不隐藏作者的意图,充满了干净利落的抽象和直截了当的控制语句。---Grady Booch
整洁的代码总是看起来像是某位特别在意它的人写的。几乎没有改进的余地。 ---Michael Feathers
简单代码书序:1.能通过所有测试;2.没有重复代码;3.体现系统中的全部设计理念;4.包括尽量少的实体,比如类,方法,函数等。 ---Ron Jeffries

作者期望
读与写花费时间的比例超过10:1,写新代码时,我们一直在读旧代码。所以要想干得快,要想早点做完,要想轻松写代码,先做到让代码易读。
让营地比你来是更干净。
如果每次check in时,代码都比签出时干净,那么代码就不会腐坏。

---------------代码规约------------------------

注释
C1:不恰当的信息。本应该在源代码控制系统,问题追踪系统或其他记录系统保存的信息通过注释来表达是不恰当的。通常作者、最后修改时间等不应在注释中出现,注释只应该描述有关代码和世界的技术性信息。
C2:废弃的注释。如果发现废弃的注释,最好尽快更新或删除掉。
C3:冗余注释。如果注释描述的是源代码充分体现了的东西,那么注释是多余的,注释应该谈及代码自身没提到的东西。(e.g: i++ //increment i 或者除了函数签名外什么都没说的Javadoc)
C4:糟糕的注释。糟糕的注释还不如不写,如果写就要字斟句酌写出最好的注释。
C5:注释掉的代码。看到注释掉的代码就删除它。
C6:表达意图尽量用代码来阐述,而不是注释,唯一真正好的注释是你想办法不去写的注释。
C7:有时,用于警告其他程序员会出现某种后果的注释也是有用的。
C8:TODO注释要通过IDE定期查看,删除不再需要的。

名称(有意义的命名)
N1:名副其实。选好名称,减少模糊度。
N2:避免误导。程序员必须避免留下掩盖代码本意的错误线索。
    (e.g:使用Unix平台专用名词做变量名/XYZControllerForEfficientHandlingOfStrings &XYZControllerForEfficientStorageOfStrings/使用小写字母I和大写字母O作为变量名)
N3:做有意义的区分(e.g: getActiveAccount();/getActiveAccounts();/getActiveAccountInfo(); 程序员如何知道该调用哪个方法)
N4:使用读得出来的名字。人类长于记忆和使用单词。(e.g 变量genymdhms 不知道是什么意思)
N5:使用IDE可快速搜索的名称。长名称胜于短名称。名称长短应该与其作用域大小相对应。对于较小的作用范围,可以用很短的名称,而对于较大作用范围就该用较长的名称
N6:避免思维映射。不应当让读者在脑海中把你的名称翻译为他们熟知的名称。专业的程序员了解,明确是王道,编写其他人能理解的代码。
N7:类名:类名和对象名应该是名词或名词短语(Customer Account AddressParser)
N8:方法名:方法名应该是动词或动词短语(postPayment deletePage save get/set/isPosted)
N9:每个概念对应一个词,并且一以贯之。(e.g 取用fetch/retrieve/get, 统一命名)
N10:别用双关语,尽量保证无歧义的名称。避免将同一个单词用于不同目的。同一个属于用于不同概念,基本上就是双关语了。
N11:使用解决方案领域名称。尽量用那些计算机科学术语,算法名,模式名,数学术语吧。
N12:使用源自所涉问题领域的名称。
N13:添加有意义的语境。(firstName lastName addrState)
N14:不要添加没用的语境。(GSDAccountAddress)
N15:采用描述行名称。不要太快取名,确认名称具有描述性。软件中的名称对于软件的可读性有90%的作用,所以要花时间取名,不可以随意对待。
N16:名称应该与抽象层级相符。不要取沟通实现的名称,取反应类或函数抽象层级的名称。(e.g boolean dial(String phoneNumber);---> boolean connect(String phoneNumber);)。
N17:尽可能使用标准命名法。如果名称基于既存约定或用法,就比较易于理解。
N18:避免编码。不应在名称中包括类型或作用范围信息。不要用匈牙利语命名法污染你的名称。
N19:名称应该说明副作用。名称应该说明函数变量或类的一切信息,不要用名称掩蔽副作用。不要用简单的动词来描述做了不知一个简单动作的函数。(e.g: getOos -->createOrReturnOos)

格式:代码格式关乎沟通/读与写花费时间的比例超过10:1
L1:大多数用200-500行的单个文件来构造出色的系统。
L2:概念间垂直方向上的区隔。几乎所有的代码都是从上往下读,从左往右读,每行展现一个表达式或一个子句,每组代码段展示一条完整的思路,这些思路用空白行区隔开来。
L3:垂直方向上的靠近。关系密切的概念应该互相靠近(G10).
L4:循环中的控制变量应该总是在循环语句中声明。
L5:实体变量应该在类的顶部声明。
L6:若某个函数调用了另外一个,就应该把它们放在一起,而且调用者应该尽可能放在被调用者上面。
L7:概念相关的代码应该放在一起。相关性越强,彼此之间的距离就该越近。
L8:横向代码长度达到100-120.
L9:赋值操作符前后加上空格,以此达到强调目的。
L10:不在函数名和做圆括号之间加空格,因为函数与参数密切相关。函数参数用空格一一隔开,表示参数是互相分离的。
L11:乘法因子之间不加空格,加减法之间用空格隔开,因为乘法的优先级高于加减法。
L12:我更喜欢不对齐的声明和赋值,如果有较长的列表需要做对齐处理,那问题就是在列表的长度上,而不是对齐上。
L13:缩进,类声明不缩进,类中的方法,成员变量相对与类缩进一个层级,方法的实现相对与方法声明缩进一个层级,代码块的思想相对与容器代码块缩进一个层级,以此类推。

函数/方法
F1:短小。函数的缩进层不应该多于一层或两层。这样的函数易于阅读和理解。
F2:只做一件事。不能再拆分出一个函数。
F3:每个函数一个抽象层级。我们想让代码拥有自顶向下的阅读顺序,我们想让每个函数后面都跟着位于下一抽象层级的函数。
F4:用多态替换Switch语句。
F5:使用描述性的函数名称。函数越短小,功能越集中,越便于起个好名字。长而具有描述性的名称比短而令人费解的名称好。追索好名称能理清你关于模块的设计思路,导致对代码的改善重构。
F6:过多的参数。函数参数的数量越少越好,没参数最好,一个次之,两个三个再次之,三个以上应该坚决避免。如果函数需要三个以上参数,说明其中一些参数应该封装为类了。
F7:使用异常替代返回错误码。抽离try/catch代码块;错误处理就是一件事;避免依赖磁铁,将错误代码的常量或枚举用异常替代,新的错误码可以通过定义新的异常派生类实现。
F8:别重复自己(DRY),通过抽象,设计模式及Aop,IOC 实现。
F9:输出参数。输出参数违反直觉,读者期望参数用于输入而非输出,如果函数要修改什么东西的状态,就修改它所在对象的状态就好了。
    (e.g: appendFooter(s); --> public void appendFooter(Report s);-->report.appendFooter();) report s,这里是输出参数,引起误解,直接在Report中增加相应的方法就好。
F10:标识参数。布尔值参数/状态参数等宣告此函数不止做了一件事,它们令人迷惑,应该消灭掉。因该分成多个方法。
F11:死函数。永不被调用的方法应该丢弃。
F12:如何写出完美的函数-->持续重构。

对象和数据结构
S1:对象把数据隐藏于抽象之后,暴露操作数据的函数。
S2:过程式代码难以添加新的数据结构,因为必须修改所有函数。面向对象代码难以添加新函数,因为必须修改所有类。
S3:模块不应了解它所操作对象的内部情形。只跟朋友谈话,不与陌生人谈话。
S4:数据传送对象。最为精炼的数据结构,是一个只有公共变量,没有函数的类。这种数据结构有时被称为数据传送对象,或DTO(Data Transfr Objects)

错误处理
E1:使用异常而非错误码
E2:你抛出的每个异常,都应该提供足够的环境说明,以便判断错误的来源和处所。在Java中,你可以从任何异常里得到堆栈踪迹(stack trace),然而堆栈踪迹却无法告诉你该失败操作的初衷。
     应创建信息充分的错误消息,并和异常一起传递出去,在消息中,包括失败的操作和失败类型。如果你的应用程序有日志系统,传递足够的信息给catch块,病记录下来。
E3:当我们在应用程序中定义异常类时,最重要的考虑应该是它们如何被捕获。解决方案:静态代理,动态代理,AOP。
E4:别返回null值,别传递null值。但是没有良好的方法能对付由调用者意外传入的null值。恰当的做法就是禁止传入null值,被调用方法中做判空的处理。(e.g if(List<Employee>没有元素,return Collections.emptyList();)


Class1:类的名称应当描述起权责。如果无法为某个类命以精确的名字,这个类大概就太长了。
Class2:类或模块应有且只有一条加以修改的理由。类只应有一个权责,只有一条修改它的理由。
Class3:系统应该有许多短小的类而不是少量巨大的类组成。每个小类封装一个权责,只有一个修改的原因,并与少数其他类一起协同达成期望的系统行为。
Class4:内聚。内聚性高,意味着类中的方法和变量互相依赖,互相结合成一个逻辑整体。当类失去了内聚性,就拆分它。
Class5:在理想的系统中,我们通过扩展系统而非修改现有代码来添加新特性。
Class6:隔离修改,我们可以借助接口和抽象类来隔离抽象的概念和实现的细节。

Java
J1:通过使用通配符避免过长的导入清单。使用通配符导入通常仍优于指定名称导入。
J2:不要继承常量。不要利用继承欺骗编程语言的作用范围规则。应该使用静态导入(import static ...)。
J3:枚举好于常量。enum可以拥有方法和字段从而成为能比常量提供更多表达力和灵活性的强有力工具。

一般性问题
G1:一个源文件中存在多种语言。理想的源文件包括且只包括一种语言。现实上,我们可能会不得不使用多于一种语言,但应该尽力减少源文件中额外语言的数量和范围。
G2:明显的行为未被发现。遵循'最小惊异原则(The Principle of Least Surprise)',函数和类应该实现其他程序员有理由期待的行为。
G3:不正确的边界行为。每种边界条件,极端情形,异常都代表了某种可能搞乱优雅而直白的算法的东西。别依赖直觉,追索每种边界条件,并编写测试。
G4:忽略安全。忽略安全相当危险,编译器警告/关闭失败测试等。
G5:重复。DRY原则(Don't repeat Yourself),重复代码代表遗漏了抽象。switch/case或if/else可以用多态或设计模式代替。23种设计模式都是消除重复的有效手段。
G6:在错误抽象层级上的代码。所有较低层级概念放在派生类中,所有较高层级概念放在其类中。只与细节实现有关的常量变量或工具函数不应该在基类中出现,基类应该对这些东西一无所知。
G7:基类依赖于派生类。基类对派生类应该一无所知。
G8:信息过多。限制类或模块中暴露的接口数量。类中的方法越少越好,函数知道的变量越少越好,类拥有的实体变量越少越好。隐藏你的数据,工具函数,常量,临时变量。尽力保持接口紧凑,通过限制信息来控制耦合度。
G9:死代码。如果你找到死代码,就将它从系统中删除掉。
G10:垂直分隔。变量和函数应该在靠近被使用的地方定义,本地变量应该正好在其首次被使用的位置上面声明,垂直距离要短。
G11:前后不一致。变量等命名前后一致,一旦贯彻,就能让代码更加易于阅读和修改。
G12:混淆视听。没有用到的变量,从不调用的函数,没有信息量的注释等等,都应该删除,保持源代码的整洁。
G13:人为耦合。不相互依赖的东西不应该耦合。 花点时间研究应该在什么地方声明函数,常量和变量,不要为了方便随手放置,然后置之不理。
G14:特性依恋。类的方法只应对其所属类中的变量和函数感兴趣,不该垂青其他类中的变量和函数。不过,有时特性依恋是种有必要的恶行。
G15:选择算子函数。选择算子参数是一种避免把大函数切分为多个小函数的偷懒做法。使用多个函数,通常优于向单个函数传递某些代码来选择函数的行为。
G16:晦涩的意图。代码要尽可能具有表现力,联排表达式,匈牙利语标记法和魔术数都遮蔽了作者的意图。
G17:位置错误的权责。软件开发者做出的最重要决定之一就是在哪里放代码,代码应该放在读者自然而然期待它所在的地方。
G18:不恰当的静态方法。通常应该倾向于选用非静态方法。如果有疑问,就是用非静态函数,如果的确需要静态函数,确保没机会打算让它有多态行为。
G19:使用解释性变量。让程序可读的最有力方法之一就是将计算过程打散成在用有意义的单词命名的变量中放置的中间值。解释性变量多比少好。
G20:函数名称应该表达其行为。如果你必须查看函数的实现或文档才知道它是做什么的,就该换个更好的函数名,或者重新安排功能代码,放到有较好名称的函数中。
G21:理解算法。获得这种知识和理解的注好途径,往往是重构函数,得到某种整洁而足具表现力,清楚呈现如何工作的东西。
G22:把逻辑依赖改为物理依赖。如果某个模块依赖于另一个模块,一来就该是物理上的而不是逻辑上的。依赖者模块不应对被依赖者模块有假定(换言之,逻辑依赖)。它应当明确的询问后者的全部信息。
G23:遵循标准约定。团队不应该用文档描述编码约定,因为代码本身提供了范例。
G24:用命名常量替代魔术数。魔术数不仅是数字,它泛指任何不能自我描述的符号,但是有些情况下,常量直接写作原始转台数字会更好(int dailyPay = hourlyRate * 8)。
G25:准确。用浮点数表示货币几近于犯罪。在代码中做决定时,确认自己足够准确,明确自己为什么要这么做,异常情况如何处理。代码中的含糊和不准确,要么是意见不同,要么是懒惰,总之都要消除。
G26:结构好与约定。命名约定很好,但却次于强制性的结构。(e.g switch case, 没人会强迫每次以同样的方式实现,但是多态抽象封装确让具体实现类必须实现所有抽象方法)。
G27:封装条件。if while 语句布尔判断逻辑需要抽离成为单独的方法。
G28:避免否定性条件。尽可能将条件表示为肯定形式。
G29:函数只做一件事。每个方法,函数只做一件事。每个类只有一个权责。
G30:掩蔽时序耦合。不要掩蔽时序耦合,参数能让时序耦合变得可见。可以按时序将早的结果作为参数传递到晚的函数中,保持时序。
G31:别随意。构建代码需要理由,而且理由应与代码结构相契合,如果结构显得太随意,其他人就会想修改它。如果结构自始至终保持一致,其他人就会使用它,并遵循其约定。
G32:封装边界条件。边界条件难以追踪,把处理边界条件的代码集中到一处,不要散落于代码中。
G33:函数应该只在一个抽象层级上。函数中的语句应该在同一个抽象层级上。拆分不同抽象层级是重构的最重要功能之一,也是最难做的一个。
G34:在较高层级放置可配置数据。位于较高层次的配置性常量易于修改,它们向下贯穿应用程序,应用程序的较低层级并不拥有这些常量的值。
G35:避免传递浏览。a.getB().getC().doSomething(); --- > myCollaborator.doSomething();

测试
T1:测试不足。一套测试应该测到所用可能失败的东西。
T2:使用覆盖率工具。使用覆盖率工具能更容易的找到测试不足的模块,类和函数。
T3:别掠过小测试。
T4:被忽略的测试就是对不确定事物的疑问。
T5:测试边界条件。特别注意测试边界条件。
T6:全面测试相近的缺陷。缺陷趋向于扎堆,在某个函数中发现一个缺陷时,最好全面测试那个函数,你可能发现缺陷不知一个。
T7:测试失败的模式有启发性。完整的测试用例,按合理的书序排列,能暴露出模式。
T8:测试覆盖率的模式有启发性。查看测试执行的代码,往往能发现失败的测试为何失败的线索。
T9:测试应该快速。竭尽所能让测试够快。
T10:保持测试整洁。测试代码和生产代码一样重要,应该像生产代码一般保持整洁。正是单元测试让你的代码可扩展,可维护,可复用。
T11:应该尽可能减少每个概念的断言数量,每个测试函数只测试一个概念。
T12:FIRST 测试规则。快速(Fast)测试应该够快;独立(Independent)测试应该相互独立;可重复(Repeatable)测试应当可在任何环境中重复通过;
        自足验证(Self-Validating)测试应该有布尔值输出,无论是通过或失败,你不应该通过查看日志文件来确认测试是否通过;及时(timely)测试应及时编写,单元测试代码早于生产代码编写。

并发编程
M1:并发是一种解耦策略。解耦目的与时机能明显的改进应用程序的吞吐量和结构。
M2:并发会在性能和编写额外代码上增加一些开销;正确的并发是复杂的,即便对于简单的问题也是如此;并发缺陷并非总能重现,所以常被看做偶然时间而忽略;并发需要对设计策略根本性修改。
M3:并发防御原则。
        分离并发相关代码和其他代码。
        谨记数据封装,严格限制对可能被共享的数据的访问。
        使用数据副本。Threadlocal.
        线程应尽可能的独立,尝试将数据分解到可被独立线程(可能在不同处理器上)操作的独立子集。(Servlet中代码使用本地变量就不会有同步问题;Spring将数据库连接等线程不安全资源通过threadlocal做分离)
        使用类库提供的线程安全群集。ConcurrentHashMap,包:java.util.concurent; java.util.concurrent.atomic; java.util.concurrent.locks;
        使用executor框架执行无关的任务
        尽可能使用非锁定方案
        有几个类不是线程安全的
M4:了解多线程模型。生产者消费者模型;读者作者模型;哲学家问题;学习这些基础算法,理解起解决方案。
M5:警惕同步方法之间的依赖。避免使用一个共享对象的多个方法。
M6:尽可能减小同步区域。考虑性能问题。
M7:很难编写正确的关闭代码。尽早考虑关闭问题,尽早令起工作正常,多预留一些时间考虑关闭过程。
M8:测试多线程代码原则。编写有潜力暴露问题的测试,在不同的编程配置,系统配置和负载条件下频繁运行。如果测试失败,跟踪错误。别因为后来测试通过了就忽略失败。
        将伪失败看做可能的线程问题;不要将系统归咎于偶发事件。
        先使非线程代码可工作;不要同时追踪非线程缺陷和线程缺陷。确保代码在线程之外可工作。
        编写可插拔的线程代码;编写可插拔的线程代码,这样就能在不同的配置环境下运行,设计切换的开关来控制。
        编写可调整的线程代码;要获得良好的线程平衡,常常需要试错。通过配置实现不同环境和线程数量的调整,允许线程依据吞吐量和系统使用自我调整。
        运行多余处理器数量的线程;运行多于处理器或处理器核心数量的线程,任务切换越频繁,越有可能找到错过临界区或导致死锁的代码。
        在不同平台上运行;尽早并经常的在所有目标平台上运行线程代码。
        调整代码并强迫错误发生;
M9:装置试错代码。可以装置代码,增加对Object.wait(); Object.sleep(); Object.yield(); Object.priority(); 等方法的调用,改变代码执行顺序,增加侦测到缺陷的可能性。

环境
ENV1:需要多少步完成构建。 应当能用单个命令签出系统并用单个命令构建它。(e.g: svn get mySystem/cd mySystem/ant all)
ENV2:需要多少步做到测试覆盖。能够通过单个命令就可以运行全部单元测试,并且能快速轻易直接了当的做到。

迭代
I1:运行所有测试。全面测试病持续通过所有测试的系统,就是可测试的系统。不可测试的系统同样不可验证,不可验证的系统绝不应该部署。
I2:持续重构。提升内聚性,降低耦合度,切分关注面,模块化系统性关注面,缩小函数和类的尺寸,选用更好的名称等

转载于:https://my.oschina.net/ysk/blog/892799

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值