出自《c++程序设计语言》作者说到
我喜欢优雅和高效的代码。代码逻辑应当直截了当,叫缺陷难以隐藏;尽量减少依赖关系,使之便于维护;依据某种分层战略完善错误处理代码;性能调至最优,省得引诱别人做没规矩的优化,搞出一堆混乱来。整洁的代码只做妤一件事
OTI公司创始人,Eclipse 战略教父,Dave Thomas说到:
整洁的代码应可由作者之外的开发者阅读和增补。它应通过所有单元测试和验收测试。它使用有意义的命名。它只提供一种非多种做一件事的途径。它只有尽量少的依赖关系、明确地定义和提供清晰、尽量少的AP,代码应通过其字面表达含义,因为不同的语言导致并非所有必需信息均可通过代码自身清晰表达
- 能通过所有测试
- 没有重复代码
- 体现系统中的全部设计理念
- 包括尽量少的实体,比如类、方法、函数等
int d; //已用时间天数
比如:上面这句代码名称没有什么实际含义,无法表达时间消逝和按照天来计算。
int elapsedTimeInDay = 1;
以上的这段代码不需要注释却也可以让人很清楚明白的理解其含义 也就是可以见名知意。
下面这是一个代表课程的数组,但是我们array没有明确的意思。我们应该直接写成能够直接看明白的名称。
String[] array=new String[]{"课程1","课程2"};
String[] courseArray=new String[]{"课程1","课程2"};
命名不能有歧义,并且避免太过于相似导致难以区分
String[] courseList = new String[]{"课程1", "课程2"};// 容易误导
String accountList = “”;
其中courseList、accountList不是一个List却命名成List,容易给人带来误导,直接写成"courseArray"或者真正用List容器来存放。
添加数字的反例:
public static void copyChars(char[] a1, char[] a2){
for(int i = 0; i < a1.length; i++){
a2[i] = a1[i];
}
}
有意义的:
public static void copyChars(char[] source, char[] target){
for(int i = 0; i < source.length; i++){
target[i] = source[i];
}
}
BeanUtils.copyProperties(source,target)
使用废话的反例:存在类名Product,ProductInfo,ProductData。名称不同,但意思无区别。废话都是冗余。要尽量让读者能鉴别不同之处方式来区分。
例如: (生成日期,年月日时分秒) 变量命名时 gennerationTimeStamp 就比genymdhms好的多,前者比后者读起来好得多,也更好理解。
名称长短应与其作用域大小相对应。若变量或常量可能在代码中多处使用,则应赋其便于搜索的名称
PhoneNumber phoneString;//名称不加类型,类型变化时,名称并不跟着变化
不必用m_前缀来标明成员变量
private String m_des;
比如传统上惯用单字母名称做循环计数器。所以就不要给一些非计数器的变量命名为:i、j、k等
类名 应该是名词或者名词短语,
避免使用Manager、Processor 类名不应该是动词
避免使用Data、Info这样的类名
方法名应当是动词或者动词短语。如postPayment、deletePage或save
避免实用get,find,fetch多个命名定义查询方法,可以对于查询同一使用getXXX
有时可能使用add并不合适,比例insert、append。add表示完整的新添加的含义。
可以把相关的变量放到一个类中,使用这个类来表明语境、或者添加前缀来提供语境
private String firstName;
private String lastName;
private String addrFirstName;
private String addrLastName;
名字中带有项目的缩写,这样完全没有必要。比如有一个名为“加油站豪华版”(Gas Station Deluxe)的项目, 在其中给每个类添加GSD前缀就不是什么好策略。
- 降低代码的模糊度,明确说明代码的用途
- 做有意义的区分,只有意义不同时才使用不同的名字
- 使用可搜索的名称:用宏定义数字比直接用数字好,避免使用单字母变量和数字常量
- 避免成员变量使用类名前缀
- 类名和对象名一般都是名词和名词短语
- 方法名一般是动词和动词短语;get,set,is前缀
- 添加有意义的语境:使用有意义的前缀
- 命名要精确:不要添加无意义的语境
函数的第一规则是要短小。第二条规则是还要更短小。
函数应该只做一件事,做好这件事。
A方法中调用了B方法。B方法应该跟在A方法后面,如果A中调用了多个方法,这些方法也应该按照一定逻辑顺序排在A后面。
长而具有描述性的名称,要比短而令人费解的名称好。长而具有描述性的名称,要比描述性的长注释好。使用某种命名约定,让函数名称中的多个单词容易阅读,然后使用这些单词给函数取个能说清其功用的名称
越少越好。 尽量不要有输出参数,而是将输出设置为返回值。 如果参数较多的时候可以考虑使用类进行封装。
最理想的参数量是零个,其实是一个,再次是二,应该尽量避免三个及以上的参数
最好不要像函数传入布尔类型的值,这样做,方法签名立刻变得复杂起来,标识为true会这样做,标识为false会那样做。应该将函数一分为二。
函数要么做什么事,要么回答做什么事,但两者不可得兼。函数应该修改某对象的状态,或者返回改对象的有关信息,两样常干常会导致混乱。
这是判断某个属性是否为为某个值,还是给属性设置某个值呢?
public boolen set(String attribute,String value)
修改方案,把指令分隔开来,防止混淆的发生
if (attributesExists("userName")){
setAttribute("userName","aihe")
}
try/catch会把程序弄的丑陋不堪,搞乱了代码结构,把错误与正常流程混为一谈。最好把tray和catch的主体部分抽离出来,另外形成函数。
写代码和写别的东西很像。在写论文或文章时,你先想什么就写什么,然后再打磨它。初稿也许粗陋无序,你就斟酌推敲,直至达到你心目中的样子。我写函数时,一开始都冗长而复杂。有太多缩进和嵌套循环。有过长的参数列表。名称是随意取的,也会有重复的代码。不过我会配上一套单元测试,覆盖每行丑陋的代码。然后我打磨这些代码,分解函数、修改名称、消除重复。我缩短和重新安置方法。有时我还拆散类。同时保持测试通过。
最后,遵循本章列出的规则,我组装好这些函数。我并不从一开始就按照规则写函数… 我想没人做得到。
别给糟糕的代码写注释,重新写吧!
有些注释是必须的,也是有利的。来看看一些我认为值得写的注释。不过要记住,唯真正好的注释是你想办法不去写的注释。
比如:版权以及著作权的声明
提供信息的注释
/** Returns the number of key-value mappings in this map.
If the map contains more than Integer.MAX_VALUE elements,
returns Integer.MAX_VALUE.
Implementation Requirements:
**/
This implementation returns entrySet().size().
public int size() {
return entrySet().size();
}
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
把某些晦涩难明的参数或返回值的意义翻译为某种可读形式
用于警告其他程序员会出现某种后果
TODO是一种程序员认为要做的,但由于某些原因还没做的工作。
如果你在编写公共API,就该为它编写良好的javadoc
展示的简单函数,其头部位置的注释全属多余。都这段注释花的时间没准比读代码花的时间还要长
每个函数都要注释,或者每个变量都要注释的规矩是愚蠢可笑
private String zoo = "" //动物//
尽力不要注释代码 因为 其他人不敢删除注释的代码
现在不建议自己写html 标记代码
源文件要像报纸一样,短小而精悍。名称简单且一目了然。源文件最顶部给出高层次的概念和算法,细节从上往下渐次展开,直至找到源文件中最底层的函数和细节
关联性高的代码块应该与其他有一定的间隔,如隔一个空格行,这样逻辑更清晰,读起来更轻松。
相互关联的代码应该放在一起形成一个代码块
关系密切的概念应该相互靠近
- 变量声明:局部变量应尽可能靠近其使用位置,循环中的变量应该在循环中声明; 全局变量应该在类顶部声明
- 调用函数:被调用的函数应该放在调用函数的下面第一位置。
- 概念相关:概念类似的代码应该放在一起。例如几个名字很像,功能类似的方法。
尽力按照IDEA 分割线为标准
空格字符把彼此紧密相关的事物连在一起,也把相关性较弱的事物分隔开。例如:
private void measureLine(String line) {
lineCount++;
int lineSize = line.length();
totalChars += lineSize;
lineWithHistogram.addLine(lineSize, lineCount);
}
IDEA 快捷键也可以自动实现改格式。
public Result getUserDto(User user) {
try {
Logger.param("user", user).desc("获取用户基本信息开始").info();
List<UserEntity> userEntity = userDao.getUserInfo(user);
LogBuilder.buildDefault().param("userEntity", userEntity).desc("获取用户基本信息结束").info();
return ResultUtil.success(user);
} catch (Exception ex) {
Logger.param("user", user).desc("获取用户基本信息出现异常").error(ex);
return ExceptionUtil.serviceException();
}
}
对于java中的异常使用try{}catch{}捕获时,要判断哪些代码可能出错只对那部分代码进行捕获,避免捕获太多代码影响性能。
必须避免空指针异常
正例:if(obj!=null){}
反例:try{obj.method()}catch{}
try {
if (!checkParamIsLegal(userDto)) {
return null;
}
UserEntity userEntity = buildUserEntity(recommendRequest, questionDto);
return StringEscapeUtils.unescapeJavaScript(JSONObject.toJSONString(userEntity));
} catch (Exception e) {
LogBuilder.buildDefault().desc("checkParamIsLegal is failed ")
.param("userEntity", userEntity)
.error(e);
return null;
}
一个方法返回null,会给上层代码增加严重的null处理负担,而且一旦遗漏对null的处理,就会导致难以排查的错误,所以尽量不要在方法中返回一个null。
除非API要求你传null值进行业务流程区分,否则尽量不要往函数中传null参数,因为这很有可能导致被调用函数抛出难以排查的空指针异常
测试应该够快。测试应该能快速运行。测试运行缓慢,你就不会想要频繁地运行它。如果你不频繁运行测试,就不能尽早发现问题,也无法轻易修正,从而也不能轻而易举地清理代码。最终,代码就会腐坏。
测试应该相互独立。某个测试不应为下一个测试设定条件。每个测试应该可以独立运行,及以任何顺序运行。当测试互相依赖时,前一个没通过就会导致一连串的测试失败,使问题诊断变得困难,隐藏了后面的错误
测试应当可在任何环境中重复通过。你应该能够在生产环境、质检环境中运行测试,也能够在无网络的列车上用笔记本电脑运行测试。如果测试不能在任何环境中重复,你就总会有个解释失败的借口。当环境条件不具备时,你就会无法运行测试。
测试应该有布尔值输出。无论是通过或失败,不应该通过查看日志文件来确定测试是否通过。不应该通过手工对比两个不同的文本文件来确认测试是否通过。如果测试不能自足验证,对失败的判断就会变得依赖主观,而运行测试也需要更长的手工操作时间
测试应及时编写。单元测试应该恰好在使其通过的生产代码之前编写。如果在编写生产代码之后编写测试,你就会发现生产代码难以测试。你可能会认为某些生产代码本身难以测试。你可能不会去设计可测试的代码。
重构就是在不改变软件系统外部行为的前提下,改善它的内部结构
名词:
对软件内部结构的一种调整,目的是在不改变软件可观察行为的前提下,提高其可理解性,降低其修改成本
动词:
使用一系列重构手法,在不改变软件可观察行为的前提下,调整其结构
什么时候该开始重构?当闻到代码的“坏味道”,便可以开始重构。而具体的开始时间并没有完全的标准可以衡量,这完全取决于你自己,可以随时随地,或者是将来的某一个时间。重构不分大小,小到修改一个函数名字,也是很好的一个开始 。
(parameters) -> expression或(parameters) ->{statements; }
List names = new ArrayList();
names.add("Google");
names.add("Runoob");
names.add("Taobao");
names.add("Baidu");
names.add("Sina");
names.forEach(System.out::println);
private <R> R executeCommand(Function<Jedis, R> commond) {
Jedis jedis = getJedis();
try {
if (jedis != null) {
return commond.apply(jedis);
}
} catch (Exception e) {
LOGGER.error("executeCommond failed : ", e);
} finally {
releaseJedis(jedis);
}
return null;
}
public String set(String key, String value, int time) {
return executeCommand(jedis -> jedis.set(key, value, SetParams.setParams().ex(time)));
}
public static void main(String[] args) {
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
List<String> filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList());
List<String> strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl");
// 获取空字符串的数量
int count = (int) strings.parallelStream().filter(string -> string.isEmpty()).count();
}
目标:实现不同支付方式业务处理,微信、支付宝、QQ钱包
public Result<Object> pay(PayBusiness payBusiness) {
Result<Object> responseResult = new Result<>();
if (Objects.equals(PayEnum.ALI_PAY.getPayCode(), payBusiness.getPayCode())) {
buildAliResult(payBusiness, responseResult);
} else if (Objects.equals(PayEnum.WX_PAY.getPayCode(), payBusiness.getPayCode())) {
buildWxResult(payBusiness, responseResult);
} else if (Objects.equals(PayEnum.QQ_PAY.getPayCode(), payBusiness.getPayCode())) {
buildQqResult(payBusiness, responseResult);
}
return responseResult;
}