好好的“代码优化”是怎么一步步变成“过度设计”的
有一天Review师妹的代码,看到一行很难看的代码,毕竟师妹刚开始转JAVA,一些书写小习惯还是要养成,所以锱铢必较还是有必要的,于是给出了一些优化思路的建议,以及为什么要这么做。建议完后,我并没有停下”追求极致“的脚步,随着不断的思考,发现这段代码的优化慢慢变得五花八门起来了,完成了一次“代码优化”到“过度设计”的典型思考过程,这过程中涉及了很多Java的语法糖及设计模式的东西,很典型,能启发思考,遂记录下来。
public Integer parseSaleType(String saleTypeStr){
if(saleTypeStr == null || saleTypeStr.equals("")){
return null;
}
if(saleTypeStr.equals("JX")){
return 1;
}
return null;
}
▐ 有函数式方法的尽量用
//saleTypeStr == nullObjects.isNull(saleTypeStr)
▐ 判断字符串为空不要自己写容易漏逻辑,尽量使用现成的方法
//if(saleTypeStr == null || saleTypeStr.equals(""))
if(StringUtils.isBlank(saleTypeStr))
▐ equals判定,常量写前面
//if(saleTypeStr.equals("JX"))
if("JX".equals(saleTypeStr))
▐ 少用魔法值,定义常量
private static final String JX_SALE_TYPE_STR = "JX";
private static final Integer JX_SALE_TYPE_INT = 1;
▐ 无状态方法,可选择定义为类静态
//public Integer parseSaleType(String saleTypeStr)public static Integer parseSaleType(String saleTypeStr)
逻辑简化
▐ 明确主体逻辑
private static final String JX_SALE_TYPE_STR = "JX";
private static final Integer JX_SALE_TYPE_INT = 1;
public static Integer parseSaleType(String saleTypeStr){
if(JX_SALE_TYPE_STR.equals(saleTypeStr)){
return JX_SALE_TYPE_INT;
}
return null;
}
▐ 语法简化:三元运算符
public static Integer parseSaleType(String saleTypeStr){
return JX_SALE_TYPE_STR.equals(saleTypeStr) ? JX_SALE_TYPE_INT : null;
}
▐ 语法简化:Optional
这个场景范式也满足,【可能为空,有后续处理,有条件,有缺省值】,Optional也算完美契合。
public static Integer parseSaleType(String saleTypeStr){
Optional.ofNullable(saleTypeStr).filter(JX_SALE_TYPE_STR::equals).map(o -> JX_SALE_TYPE_INT).orElse(null);
}
▐ 方法独立存在的必要性讨论
其实语法简化到三元运算符和Optional这一步,如果一个方法体内只有这一行,这个方法独立存在的必要性的就开始存疑了,如果所有的转换流程都能收束在工程中的某个环节上,且保证这个方法的引用仅存在一处,那么这一行代码其实放在主干代码上更好,防止来回跳转的代码阅读障碍,当然这也仅仅是在现状下的讨论,如果存在且不仅限于以下几种状况时还得独立出来:
-
未来除了一种逻辑分支外,还会扩展其他分支,并且有被扩展的可能; -
虽然还是一种逻辑分支,但是判断的内容变长了,跟上下文和调用状态有关; -
虽然还是一种逻辑分支,但是逻辑总在调整; -
一处定义,多点引用;
▐ 值枚举构建
考虑继续将入参的所有可能和出参的所有可能,可以构建为两组枚举值,这样所有的同簇常量就被放到一起了。
public enum SaleTypeStrEnum{
JX,
// OTHERS
;
}
@AllArgsConstructor
@Getter
public enum SaleTypeIntEnum{
JX(1),
// OTHERS
;
private Integer code;
}
public enum SaleTypeStrEnum{
JX,
// OTHERS
;
public static SaleTypeStrEnum getByName(String saleTypeStr){
for (SaleTypeStrEnum value : SaleTypeStrEnum.values()) {
if(value.name().equals(saleTypeStr)){
return value;
}
}
return null;
}
}
public enum SaleTypeStrEnum{
JX,
// OTHERS
;
/**
* 预热转换关系到内存
*/
private static Map<String, SaleTypeStrEnum> NAME_MAP = Arrays.stream(SaleTypeStrEnum.values()).collect(Collectors.toMap(SaleTypeStrEnum::name, Function.identity()));
public static SaleTypeStrEnum getByName(String saleTypeStr){
return NAME_MAP.get(saleTypeStr);
}
}
这样每次检索就是O(1)了,那么最终方法体内也能使用switch管理原本的if-else
public static Integer parseSaleType(String saleTypeStr){
switch(SaleTypeStrEnum.getByName(saleTypeStr)){
case JX:return SaleTypeIntEnum.JX.getCode();
// OTHERS
default:return null;
}
}
▐ 关系枚举构建
再仔细思考下,其实这里在描述的内容,无论是哪个枚举描述的内容都是同一件事物,方法本身就是描述两个不同编码的转换关系,且转换关系本身就是单向的,且映射路径极度简单,所以简单化一点,可以直接构建转换关系枚举。
@Getter
@AllArgsConstructor
public enum SaleTypeRelEnum {
// 不在分别定义两类变量,而是直接定义变量映射关系
JX("JX", 1),
// OTHERS
;
private String fromCode;
private Integer toCode;
private static Map<String, SaleTypeRelEnum> FROM_CODE_MAP = Arrays.stream(SaleTypeRelEnum.values()).collect(Collectors.toMap(SaleTypeRelEnum::getFromCode, Function.identity()));
public static SaleTypeRelEnum get(String saleTypeStr){
return FROM_CODE_MAP.get(saleTypeStr);
}
public static Integer parseCode(String saleTypeStr){
return Optional.ofNullable(SaleTypeRelEnum.get(saleTypeStr)).map(SaleTypeRelEnum::getToCode).orElse(null);
}
}
“万事不决,上设计模式”
哎~就是玩儿~
▐ 策略模式-简单实现
首先,依然将传入的字符串作为路由依据,但是传入的内容为了防止有未来扩展,所以构造一个上下文,策略本身基于上下文来处理,借助上文定义的值枚举做策略路由。
/**
* 定义策略接口
*/
public interface SaleTypeParseStrategy{
Integer parse(SaleTypeParseContext saleTypeParseContext);
}
/**
* 策略实现
*/
public class JxSaleTypeParseStrategy implements SaleTypeParseStrategy{
@Override
public Integer parse(SaleTypeParseContext saleTypeParseContext) {
return SaleTypeIntEnum.JX.getCode();
}
}
/**
* 调用上下文
*/
@Data
public class SaleTypeParseContext{
private SaleTypeStrEnum saleTypeStr;
private SaleTypeParseStrategy parseStrategy;
public Integer pasre(){
return parseStrategy.parse(this);
}
}
public static Integer parseSaleType(String saleTypeStr){
SaleTypeStrEnum saleTypeEnum = SaleTypeStrEnum.getByName(saleTypeStr);
SaleTypeParseContext context = new SaleTypeParseContext();
context.setSaleTypeStr(saleTypeEnum);
switch(saleTypeStr){
// 策略路由
case JX:context.setParseStrategy(new JxSaleTypeParseStrategy());break;
// 继续扩展
default:return null;
}
return context.parse();
}
当然,如果是这种没有上下文强依赖的策略,无论是静态单例还是Spring单例都会是一个不错的选择。SaleTypeParseContext本身可以继续扩展内容和其他属性继续丰富参数,策略实现中也可以继续针对更多参数扩充逻辑。
▐ 策略工厂-手动容器
策略是个好东西,但是简单实现下,这里依然将策略实现的路由过程交给了调用方来做,那么每增加一种实现,调用点还要继续改,要是恰好有若干调用点就完犊子了,并不优雅,所以搞个中间层容器工厂,解耦一下依赖。
@Component
public static class SaleTypeParseStrategyContainer{
public final static Map<SaleTypeStrEnum, SaleTypeParseStrategy> STRATEGY_MAP = new HashMap<>();
@PostConstruct
public void init(){
STRATEGY_MAP.put(SaleTypeStrEnum.JX, new JxSaleTypeParseStrategy());
// 继续拓展
}
public Integer parse(SaleTypeParseContext saleTypeParseContext){
return Optional.ofNullable(STRATEGY_MAP.get(saleTypeParseContext.getSaleTypeStr())).map(strategy-> strategy.parse(saleTypeParseContext)).orElse(null);
}
}
容器内手动创建各个策略的实现的单例后进行托管,那调用方只需要去构建上下文就好了,实际调用的方法更换为 SaleTypeParseStrategyContainer::parse,那后续无论策略如何丰富,调用方都不需要再感知这部分变化。后续出现了新的策略实现,则在工厂内继续追加路由表即可。
▐ 注册与发现&策略工厂-Spring容器
如果考虑到策略会依赖Spring的bean和其他有状态对象,那么这里也可以改成Spring的注入模式,同时继续将“支持哪种情况”由托管方容器移动至策略内部,改成由策略实现自身去注册到容器中。
public interface SaleTypeParseStrategy{
Integer parse(SaleTypeParseContext saleTypeParseContext);
// 所支持的情况
SaleTypeStrEnum support();
}
@Component
public class JxSaleTypeParseStrategy implements SaleTypeParseStrategy{
@Override
public Integer parse(SaleTypeParseContext saleTypeParseContext) {
return SaleTypeIntEnum.JX.getCode();
}
@Override
public SaleTypeStrEnum support() {
return SaleTypeStrEnum.JX;
}
}
@Component
public static class SaleTypeParseStrategyContainer{
public final static Map<SaleTypeStrEnum, SaleTypeParseStrategy> STRATEGY_MAP = new HashMap<>();
@Autowired
private List<SaleTypeParseStrategy> parseStrategyList;
@PostConstruct
public void init(){
parseStrategyList.stream().forEach(strategy-> STRATEGY_MAP.put(strategy.support(), strategy));
}
public Integer parse(SaleTypeParseContext saleTypeParseContext){
return Optional.ofNullable(STRATEGY_MAP.get(saleTypeParseContext.getSaleTypeStr())).map(strategy-> strategy.parse(saleTypeParseContext)).orElse(null);
}
}
这样的话,连容器都不用改了,追加策略实现的改动只与当前策略有关,调用方和容器类都不需要感知了,但是缺点就在于如果有俩策略支持的情况相同,取到的是哪个就听天由命了~
▐ 注册与发现&责任链
public interface SaleTypeParseStrategy{
Integer parse(SaleTypeParseContext saleTypeParseContext);
// 用于判断是否支持
boolean support(SaleTypeParseContext saleTypeParseContext);
}
@Component
public class JxSaleTypeParseStrategy implements SaleTypeParseStrategy{
@Override
public Integer parse(SaleTypeParseContext saleTypeParseContext) {
return SaleTypeIntEnum.JX.getCode();
}
@Override
public boolean support(SaleTypeParseContext saleTypeParseContext) {
return SaleTypeStrEnum.JX.equals(saleTypeParseContext.getSaleTypeStr());
}
}
@Component
public static class SaleTypeParseStrategyContainer{
@Autowired
private List<SaleTypeParseStrategy> parseStrategyList;
public Integer parse(SaleTypeParseContext saleTypeParseContext){
return parseStrategyList.stream()
.filter(strategy->strategy.support(saleTypeParseContext))
.findAny()
.map(strategy->strategy.parse(saleTypeParseContext))
.orElse(null);
}
}
-
植入Diamond走走动态配置开关的思路;
-
植入QLExpress搞搞逻辑表达式的思路; -
把策略实现改成HsfProvider走分布式调用思路; -
借助一些成熟的网关走服务路由的的调用思路;
本文分享自微信公众号 - 大淘宝技术(AlibabaMTT)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
为什么需要在 OpenShift 上部署企业级 Ingress Controller
原文作者:Max Mortillaro of GigaOm 原文链接:为什么需要在 OpenShift 上部署企业级 Ingress Controller 转载来源:NGINX 中文官网 NGINX 唯一中文官方社区 ,尽在nginx.org.cn Red Hat OpenShift作为业界备受推崇的 Kubernetes 平台解决方案,凭借其全面的功能集、稳健的架构和企业级支持获得了诸多企业的青睐。毫无疑问,这些企业也在寻求企业级流量控制功能及自动化,以增强其 Kubernetes 平台并加快应用开发和部署速度。 Kubernetes 要求使用 Ingress 接口来处理进入集群的外部流量。在实践中,访问 Kubernetes 应用的外部客户端通过网关进行通信,该网关将四层至七层的流量暴露给集群内的 Kubernetes 服务。 要实现这一点,Ingress 资源中的流量路由规则需要由Ingress controller来实施。没有 Ingress controller,Ingress 将一无用处。在下图中,Ingress controller 将所有外部流量发送到单个 Kubern...
- 下一篇
得物云原生容器技术探索与落地实践
一、前言 得物 App 作为互联网行业的后起之秀,在快速的业务发展过程中基础设施规模不断增长,继而对效率和成本的关注度也越来越高。我们在云原生技术上的推进历程如图所示,整体上节奏还是比较快的。 从 2021 年 8 月开始,我们以提升资源使用率和资源交付效率为目标,开始基于云原生技术建设整个服务体系的高可用性、可观测性和高运维效率,同时要保证成本可控。在容器化过程中我们遇到了很多的挑战,包括:如何将存量的服务在保持已有研发流程不变的情况下,做到容器化部署和管理;容器化之后如何做到高效地运维;如何针对不同的业务场景,提供不同的容器化方案等等。此外,通过技术手段实现持续的成本优化是我们的长期目标,我们先后建设落地了画像系统、混部方案和调度优化等方案。本文把得物在推进云原生容器技术落地过程中相关方案和实践做一些总结和梳理,欢迎阅读和交流。 二、云原生应用管理 云原生应用管理方式 容器与 ECS 的资源形态是有差异的,所以会造成在管理流程上也会有不同之处。但是为了尽可能降低容器化带来的使用体验上的差异,我们参考业内容器应用 OAM 模型的设计模式,对容器的相关概念做了屏蔽和对等解释。例如:以“...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- CentOS7,8上快速安装Gitea,搭建Git服务器
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- SpringBoot2全家桶,快速入门学习开发网站教程
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS8编译安装MySQL8.0.19
- CentOS7,CentOS8安装Elasticsearch6.8.6