个人精进系列-好的代码-审美/函数
审美
好的代码应当“看上去养眼”
一致性
public class PerformanceTester {
public TcpConnectionSimulator wifi = new TcpConnectionSimulator(
500,
80);
public TcpConnectionSimulator t3LongNameFiber =
new TcpConnectionSimulator(
45000,
10);
public TcpConnectionSimulator cell = new TcpConnectionSimulator(
100,
400);
}
调整
重新排版来保持一致和紧凑
public class PerformanceTester {
public TcpConnectionSimulator wifi =
new TcpConnectionSimulator(
500,
80);
public TcpConnectionSimulator t3LongNameFiber =
new TcpConnectionSimulator(
45000,
10);
public TcpConnectionSimulator cell =
new TcpConnectionSimulator(
100,
400);
}
换行
定义:
public ResourceHandlerMapping(ResourceLoader staticResourceLoader, String baseDir, String mappingPath)
使用:
new ResourceHandlerMapping(staticResourceLoader,"/Volumes/Mac/Users/jaywu/Documents/webapp/",DEFAULT_MAPPING_PATH);
调整
重新排版来保持一致和紧凑
public ResourceHandlerMapping(ResourceLoader staticResourceLoader,
String baseDir,
String mappingPath)
使用:
new ResourceHandlerMapping(
staticResourceLoader,
"/Volumes/Mac/Users/jaywu/Documents/webapp/",
DEFAULT_MAPPING_PATH
);
分组
例1
public class Demo {
public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
private MultipartResolver multipartResolver;
private FlashMapManager flashMapManager;
private LocaleResolver localeResolver;
public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver";
private MultipartResolver multipartResolver;
public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver";
}
调整
把声明按块组织起来
public class Demo {
public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver";
public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver";
private MultipartResolver multipartResolver;
private LocaleResolver localeResolver;
private MultipartResolver multipartResolver;
private FlashMapManager flashMapManager;
}
例2
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
调整
把代码分成“段落”
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
原则
按代码逻辑的关联、紧密程度分组
IDEA
- 修复IDEA的黄色警告
- 越新的版本,可以给出越多改进的地方
- 代码分割线时刻提醒你的代码长度
函数
- 在面向对象语言中,函数称之为方法。
- 编写好函数是编写好代码的基础。
- 一个系统容易腐化的部分正是函数,不解决函数的复杂性,就很难解决系统的复杂性。
意图提取
将逻辑中的重要意图单独提取函数,使整体逻辑清晰,避免陷入细节。
例
if(a > 0 && b < 1) {
c = 1;
d = 2;
}
调整
if(isItemEnabled()) {
changeOrderStatus();
}
函数参数
- 参数越少,越容易理解,越容易使用和测试
- 参数较多时,考虑是否可以封装为类
例
Line makeLine(String startX,
String startY,
String endX,
String endY);
调整
public class Point{
double x;
double y;
}
Line makeLine(Point start, Point end);
短小的函数
- 不超过25行
- 严格执行标准,代码质量可以得到快速提升
要写出精简的函数,可以牢记以下原则:
- 精简辅助代码
- 组合函数模式
精简辅助代码
定义
必不可少的非核心代码即为辅助代码
- 打印日志
- 鉴权
- 降级
- 缓存操作
- 非空判断
- ……
重要性
减少对业务代码的干扰,防止淹没在辅助代码,直观地体现业务逻辑。
优化判空
判空代码非常干扰阅读流畅性
public void handle(A a) {
String value = null;
if (a != null) {
B b = a.b();
if (b != null) {
C c = b.c();
if (c != null) {
D d = c.d();
if (d != null) {
value = d.value();
}
}
}
}
// 重要的业务代码
...
}
调整
意图提取,防止淹没业务代码
public void handle(A a) {
String value = getValue(a)
// 重要的业务代码
}
private String getValue(A a) {
if (a != null) {
B b = a.b();
if (b != null) {
C c = b.c();
if (c != null) {
D d = c.d();
if (d != null) {
return d.value();
}
}
}
}
return null;
}
尽早退出异常流程
private String getValue(A a) {
if(a == null) {
return null;
}
B b = a.b();
if(b == null) {
return null;
}
C c = b.c();
if(c == null) {
return null;
}
D d = c.d();
if(d == null) {
return null;
}
return d.value();
}
隐藏技术细节
- 抽象通用框架
- 自动化日志打印
- 自动化鉴权处理
- 全局异常处理
- 隔离第三方依赖(DDD)
- 中间件依赖
- 第三方接口
组合函数模式
定义
将大函数拆成多个子函数的组合
例
public Source handle(Context context) {
List<Source> sources = handlers.stream()
.map(it -> it.handle(context))
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (sources.isEmpty()) {
return null;
}
SelectorBinding selectorBinding = new ListBinding(
sources.stream()
.map(it -> it.getSourceType().value())
.collect(Collectors.toList())
);
String selectedValue = selector.select(selectorConfigId(), selectorBinding);
return sources.stream()
.filter(it -> StrUtil.equals(it.getSourceType().value(), selectedValue))
.findFirst()
.orElse(null);
}
- 17行代码,属于短小的函数
- 无法快速理解整体业务逻辑
调整
- 提炼执行步骤概要,细节分散在私有函数
- 一个私有函数尽可能只做一件事情
public Source handle(Context context) {
List<Source> sources = filterEffectiveSources(context);
if (sources.isEmpty()) {
return null;
}
String selectedValue = selectSourceValue(sources);
return sourceOf(sources, selectedValue);
}
调整后
- 像在看一本书,入口函数是目录,指向各自的私有函数
- 每个私有函数职责单一,代码精炼、易于复用
抽象层次一致性
- 函数体内容在同一抽象层次
- 高层抽象和实现细节不能混杂
例
public Source handle(Context context) {
List<Source> sources = filterEffectiveSources(context);
if (sources.isEmpty()) {
return null;
}
SelectorBinding selectorBinding = new ListBinding(
sources.stream()
.map(it -> it.getSourceType().value())
.collect(Collectors.toList())
);
String selectedValue = selector.select(selectorConfigId(), selectorBinding);
return sourceOf(sources, selectedValue);
}
调整
- selectedValue的处理过程与其他函数不在一个层次
public Source handle(Context context) {
List<Source> sources = filterEffectiveSources(context);
if (sources.isEmpty()) {
return null;
}
String selectedValue = selectSourceValue(sources);
return sourceOf(sources, selectedValue);
}
抽象层次一致性是指导函数拆分的重要原则。
函数式编程
-
把函数作为参数传递给另一个函数
public count(Supplier<String> supplier) { }
-
减少冗余代码,让代码更简洁、可读性更好。
- 代码冗余程度(大到小)
- 经典类
- 匿名类
- 匿名类
- Lamda(匿名函数)
- 函数引用
- 代码冗余程度(大到小)
-
函数无副作用
- 没有对共享的可变数据操作
- 利用多核并行处理,无需关心线程安全问题
Java的函数式编程
Stream
long count = 0;
List<Integer> list = Arrays.asList(1, 2, 3);
for (Integer it : list) {
if (it > 2) {
count++;
}
}
调整
Stream.of(1, 2, 3)
.filter(it -> it > 2)
.count();
Optional
String value = null;
if (a != null) {
B b = a.b();
if (b != null) {
C c = b.c();
if (c != null) {
D d = c.d();
if (d != null) {
value = d.value();
}
}
}
}
调整
String value = Optional.ofNullable(a)
.map(it->it.b())
.map(it->it.c())
.map(it->it.d())
.map(it->it.value())
.orElse(null)
总结
- 函数组合容易构建复杂逻辑
- 自明的代码
- 便于复用
- 易于使用
- 更多请参考
保持精进
-
写出好的代码无需高深的思想,需要的只是工匠精神
Kent Beck说的:“我不是一个伟大的程序员,只是习惯比较好而已。”
-
只有养成精益求精、追求卓越的习惯,才能保持精进,写出好的代码
网友评论