zoukankan      html  css  js  c++  java
  • 大学毕业4年-回顾和总结(6)-技术研发-重构之法

    大部分的公司,开发项目都是作坊式的,没有产品和项目的需求分析,进而做出技术架构和详细设计。
    很多人,听到上级和老板的一个想法,就开始写代码,边写边改,甚至推倒重来。

    最终,导致的常见结果之一,项目代码混乱,新员工甚至老员工,对项目理解比较吃力。
    如果你去改造代码,改好了,没有任何功劳。改出问题了,领导、测试、产品,很可能会说你不行。
    这一点,是让很多程序员纠结的地方。

    我个人还是倾向重构的,先熟悉项目总体环境,从易到难。
    项目开发,从外部看,就是一个个的功能。
    从内部看,不就是一个个函数和API吗。
    只要输入和输出是稳定的,就不会出现重大的失误,导致牵一发动全身的不良后果。

    下面记录一些,我个人自己揣摩的一些重构方法,阅读《重构》这本书,也带给了我一些启发。

    1.命名规范化
      包名、类名、方法名、变量名,取合适的英文单词。
      比如在一个电商项目中。
      包名:com.company.shop.front.controller,前端系统的控制层
      类名:OrderController,处理订单的控制器
      方法名:findById,根据id查找对象,我个人更喜欢用get。
      变量名:List<String> memberIdList = new ArrayList<String>();
       会员id的list集合
       
      比较坑的命名:Object tagcode = map.get("tagCode");
      变量命名,没有按照Java驼峰式命名规范来写,“tagcode”和“tagCode”竟然同时存在。
      在Java内部代码变量名,如果错了还有提示,在Freemarker和JSP等界面中,字符串的值,用错了,根本没有提示。
     大小写不统一的bug,还很难发现。
      
      命名要一致:Controller、Service、Dao等,同一个人和不同人的,尽可能遵循一定的标准,阅读和修改其他人代码也更顺利。
      
    2.代码合理组织
      controller:控制器,响应请求,路由控制
      service:服务层,处理业务逻辑
      dao:数据访问层
      model:数据库模型
      bean:内部用的实体类
      util:工具代码
      interceptor:拦截器
      
      其它代码,可以按照功能等进行划分,让人一眼望去,就知道这个包下的代码,大致做了什么事情。

    3.项目合理组织
      合理拆分项目:
           移动端,mobile项目
    Web前端,front项目
    后端管理:backend项目
      合理服务拆分:
            商品服务系统:ProductService项目
    会员服务系统:UserService项目
    登录服务系统:LoginService项目
    订单服务系统:OrderService项目
      公共代码:Model模型、Util工具类

       听说淘宝和京东的电商网站,有几百上千个服务。
       
    4.使用最小最恰当的作用域
       类,大多数类用的是public,public class ProductService,如果只是在包内部使用,可以去掉public。
       字段,尽可能用private, private ProductService productService。如果需要,通过get和set方法,来获得和修改值。
       方法/函数,对外被调用的用public,只在类的内部使用的,尽可能用private。
       public void  add(){
          doAdd();
       }
       
       private void doAdd(){
       
       }

       很多人,内部代码都搞成public,乍一眼看上去,还以为外部有调用。
       必须得看看依赖,才确定。
       对外暴露了过多的接口,不该被调用的被调用了。

    5.常量提取
      "success",把作用一致并且相同的字符串,提取成常量,统一管理。
      其中一种方式:
    public class FrontConst {
    
    	public static final String COOKIE_LOGINNAME = "loginNameCookie";
    	
    	public static final String COOKIE_PASSWORD = "passWordCookie";
    	
    }


    6.重复代码提取和封装
      业务代码,提取成私有方法,内部重复使用。
      工具代码,提取到工具类中,可以复用,比如日期处理、把list转换成map。
     
     public class BizUtil {
    	/**
    	 * 把1个集合,转换成Map。用法示例:Map<String, Dictionary> dictionaryMap = BizUtil.listToMap("BIANMA", dictionarys);
    	 * @param keyName 集合元素唯一字段的名称
    	 * @param list 集合元素
    	 * @return map
    	 */
    	public static <K, V> Map<K, V> listToMap(final String keyName, List<V> list) {
    		if(CollectionUtils.isEmpty(list)){
    			return null;
    		}
    		return Maps.uniqueIndex(list, new Function<V, K>() {
    			@Override
    			public K apply(V v) {
    				try {
    					return (K)PropertyUtils.getSimpleProperty(v, keyName);
    				} catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
    					e.printStackTrace();
    					return null;
    				}
    			}
    		});
    	}
    
    	/**
    	 * 把1个集合,转换成Map。用法示例:Map<String, Dictionary> dictionaryMap =
    	 * BizUtil.listToMap(String.class, Dictionary.class, "BIANMA", dictionarys);
    	 * 
    	 * @param k
    	 *            Map的key的class
    	 * @param v
    	 *            Map的value的class,集合list元素的类型
    	 * @param keyName
    	 *            集合元素唯一字段的名称
    	 * @param list
    	 *            集合元素
    	 * @return map
    	 */
    	//这种方式,废弃了,需要多传2个参数
    	public static <K, V> Map<K, V> listToMap(Class<K> k, Class<V> v,
    			String keyName, List<V> list) {
    		Map<K, V> map = Maps.newHashMap();
    		if (CollectionUtils.isNotEmpty(list)) {
    			for (V val : list) {
    				try {
    					PropertyDescriptor pd = new PropertyDescriptor(keyName, v);
    					Method getMethod = pd.getReadMethod();// 获得get方法
    					Object o = getMethod.invoke(val);// 执行get方法返回一个Object
    					if (o != null && o.getClass().equals(k)) {
    						map.put((K) o, val);
    					}
    				} catch (IllegalAccessException | IllegalArgumentException
    						| InvocationTargetException | IntrospectionException e) {
    					e.printStackTrace();
    				}
    
    			}
    		}
    		return map;
    	}

    7.函数拆分
      1个函数,有5个参数,有2种不同的使用场景。
      同一个函数完成了2件不同的事情。
      void add(int a,int b,int c);
      换成
      void addOrder(int a,int b);
      void addProduct(int c)。
      
    8.函数合并
      2个函数,完成了类似的事情,简化成1个,

      函数是拆分,还是合并,要具体分析。
      
    9.单一职责原则
      一个项目、一个模块、一个类、一个函数,完成一件事。
      如果完成了多件事,需要进行拆分成多个独立的函数,至少内部需要进行拆分。
      比如:
      void add(int a,int b,int c)
      可以拆分成
      void add1(int a,int b)和void add2(int c)
      
      也可以拆分成
      void add(int a,int b,int c){
        add1(a,b);
    add2(c);
      }

    10.控制类和函数的行数
       一个类,如果函数太多,往往是承载了太多的功能。
       把不紧密相关的功能,堆积在了一起。
       
       一个方法,如果代码超过了100行,存在问题的可能性就增大了很多。
       在我自身的经历中,很多同事的函数,只要超过100行,一眼望去就能发现问题。
       
       另外,函数代码过多,修改bug和新增业务逻辑的时候,很容易引入新的问题。
       因此,函数拆分、降低作用域,可以保证过去稳定的代码逻辑,不会有改动。
       
    11.提前返回
      先检查错误,如果有问题,直接返回,以免嵌套过深。
    public String add(){
    		Member member = this.getMember();
    		if(StringUtils.isBlank(...){
    			return error("地址信息有误!");
    		}
    }

    经常出现下面的情况
       if(...){
           if(...){
          if(...){
      
      }
       }
       }
      return ...;
      
    12.定义枚举
      枚举是常量的进一步封装。
    public enum OrderPayStatusEnum {
    	NO("0", "未支付"), YES("1", "已支付");
    
    	private String code;
    	private String remark;
    
    	OrderPayStatusEnum(String code, String remark) {
    		this.code = code;
    		this.remark = remark;
    	}
    
    	public String getCode() {
    		return code;
    	}
    
    	public void setCode(String code) {
    		this.code = code;
    	}
    
    	public String getRemark() {
    		return remark;
    	}
    
    	public void setRemark(String remark) {
    		this.remark = remark;
    	}
    
    	public static String getPayStatus(String code){
    		if(StringUtils.isEmpty(code)){
    			return null;
    		}
    		if(code.equals(NO.getCode())){
    			return NO.getRemark();
    		}else if(code.equals(YES.getCode())){
    			return YES.getRemark();
    		}
    		return null;
    	}
    }


    13.统一API交互接口
     后端和移动端,后端和Web前端的交互,都是Result的json字符串。
     后端,所有请求,统一都是返回Result。
    public class Result {
        private Integer code;
        private String desc;
        private Object data;
    }
     多人开发的时候,如果没有架构师之类的角色,各自为战,真是乱套。

    14.模块化和依赖
      大多数项目,都会有诸如邮件、短信验证码、登录服务、图片云服务等。
      把配置文件单独拿出来,可以手动通过Spring的配置文件,配置bean。
      如果需要,引入配置文件,不引入,代码也不会报错。
      
      spring-mail-config.xml
     
    <context:property-placeholder location="file:${config_path}/config/mail.properties" ignore-unresolvable="true" />
      <bean id="javaMailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
    		<property name="host" value="${mailServerHost}" />
    		<property name="port" value="${mailServerPort}" />
    		<property name="javaMailProperties">
    			<props>
    				<prop key="mail.smtp.auth">true</prop>
    				<prop key="mail.smtp.timeout">25000</prop>
    			</props>
    		</property>
    		<!-- 发送者用户名 -->
    		<property name="username" value="${mailUserName}" />
    		<!-- 发送者密码 -->
    		<property name="password" value="${mailPassword}" />
    		<property name="defaultEncoding" value="UTF-8" />
    	</bean>
    
    	<bean id="mailClientServer" class="com.shop.common.mail.MailClientServer">
    		<property name="javaMailSender" ref="javaMailSender" />
    		<property name="configuration" ref="freeMarkerConfigurationFactory" />
    		<property name="mailFromAddress" value="${mailFromAddress}" />
    	</bean>

      还比如,咱们的图片用的阿里云的OSS。
      统一封装。
      配置文件
      oss.properties
      oss.xml
     
       	<context:property-placeholder
    		location="file:${config_path}/config/oss.properties"
    		ignore-unresolvable="true" />
    	<bean id="ossConfig" class="com.shop.common.oss.OssConfig">
    		<property name="endpoint" value="${oss.endpoint}" />
    		<property name="bucketName" value="${oss.bucketName}" />
    		<property name="accessKeyId" value="${oss.accessKeyId}" />
    		<property name="accessKeySecret" value="${oss.accessKeySecret}" />
    		<property name="imgDomain" value="${oss.imgDomain}" />
    		<property name="fileDomain" value="${oss.fileDomain}" />
    	</bean>


      Java代码
      public class OssUtil {}
      
    15.代码复用
      代码复用,是一个广泛的话题。
      前文提到的枚举、常量、函数拆分,目的之一就是为了方便代码复用。
      一个优秀的程序员,抵得上十个差的程序员,其中一个原因就是“代码复用多”。
      总是写重复的代码,把程序员这个智力密集型工作,干成了体力密集型,早晚被累死。
      
      梳理业务需求、做好业务方面的架构设计。
      另外,技术方面的架构设计,完全掌握在技术人员手中,对业务的依赖不大。
      开发一个功能时,先想好思路,技术方案,再写代码。
      
      架构复用、设计复用、工具类、枚举、常量。
      另外还有一种,非常常见的,流程复用。
      
      前端系统:
        1.构造url和参数
    2.执行请求,获得数据
    3.根据状态码,执行不同的操作
    4.正常状态,渲染数据,或执行动作
      移动端:
        流程类似
      后端:
        1.稳定并且统一的API接口
    2.业务逻辑封装
    无论内部怎么变,接口稳定,就不会影响Web前端和移动端。
      在合适的时机,内部项目拆分,项目服务化。

      重构的技巧,还是有很多的,更多还是看个人对编程的理解。
      对于有经验的程序员来说,《重构》是一本不错的书,可以用来快速加深平时对项目代码的梳理。
      对于新手来说,《重构》和设计模式,这种书,受益偏小。

    小雷-一个有独立见解的年轻人
    2016年4月16日~清明节刚过,五一又要来了
    湖北-武汉~听说最近有几条公交专线要开通了

    最近,有好多好多的想法和话题要写,时间不太够。
    更准确来说, 时间是有的,写文章,也要看心情。
    写一篇严肃一点的长篇大论,一般都需要1小时以上。
    在写作之前,大脑里面一直在不断地“打草稿”,写的过程中,需要不断发散,要有条理,还要严谨,站得住脚。

    一些问题的根源,也许是我的身价还是太低了,更多时间忙着搞钱,一些有价值的事情,只能慢慢地去做。 
  • 相关阅读:
    【转】ps墨镜哥教你怎么变成合影帝
    【转】MySQL修改密码方法总结
    【技术贴】五分钟解决打开软件提示windows正在设置Microsoft Office Profes
    【技术贴】IIS相关问题解决笔记。
    【技术贴】五分钟解决打开软件提示windows正在设置Microsoft Office Profes
    【转】小生我怕怕工具包[2010.06.17](转自52破解论坛)
    【技术贴】Realtek HD声卡下QQ语音话筒没声音解决办法
    【技术贴】所有好友的QQ空间都打不开进不去的超简单解决办法!
    ASP.NET IIS 注册工具
    关于服务器如何设置动易系统数据库路径的方法
  • 原文地址:https://www.cnblogs.com/qitian1/p/6462459.html
Copyright © 2011-2022 走看看