个人精进系列-好的代码-审美/函数

个人精进系列-好的代码-审美/函数

审美

好的代码应当“看上去养眼”

一致性

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 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 sources = filterEffectiveSources(context);

    if (sources.isEmpty()) {
        return null;
    }

    String selectedValue = selectSourceValue(sources);

    return sourceOf(sources, selectedValue);
}

调整后

  • 像在看一本书,入口函数是目录,指向各自的私有函数
  • 每个私有函数职责单一,代码精炼、易于复用

抽象层次一致性

  • 函数体内容在同一抽象层次
  • 高层抽象和实现细节不能混杂

public Source handle(Context context) {
    List 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 sources = filterEffectiveSources(context);

    if (sources.isEmpty()) {
        return null;
    }

    String selectedValue = selectSourceValue(sources);

    return sourceOf(sources, selectedValue);
}

抽象层次一致性是指导函数拆分的重要原则。

函数式编程

  • 把函数作为参数传递给另一个函数

    • public count(Supplier supplier) {
          
      }
      
  • 减少冗余代码,让代码更简洁、可读性更好。

    • 代码冗余程度(大到小)
      • 经典类
      • 匿名类
      • 匿名类
      • Lamda(匿名函数)
      • 函数引用
  • 函数无副作用

    • 没有对共享的可变数据操作
    • 利用多核并行处理,无需关心线程安全问题

Java的函数式编程

Stream
long count = 0;
List 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说的:“我不是一个伟大的程序员,只是习惯比较好而已。”

  • 只有养成精益求精、追求卓越的习惯,才能保持精进,写出好的代码

你可能感兴趣的:(个人精进系列-好的代码-审美/函数)