大部分的公司,开发项目都是做坊式的,没有产品和项目的需求分析,进而作出技术架构和详细设计。
不少人,听到上级和老板的一个想法,就开始写代码,边写边改,甚至推倒重来。
最终,致使的常见结果之一,项目代码混乱,新员工甚至老员工,对项目理解比较吃力。
若是你去改造代码,改好了,没有任何功劳。改出问题了,领导、测试、产品,极可能会说你不行。
这一点,是让不少程序员纠结的地方。
我我的仍是倾向重构的,先熟悉项目整体环境,从易到难。
项目开发,从外部看,就是一个个的功能。
从内部看,不就是一个个函数和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小时以上。
在写做以前,大脑里面一直在不断地“打草稿”,写的过程当中,须要不断发散,要有条理,还要严谨,站得住脚。
一些问题的根源,也许是个人身价仍是过低了,更多时间忙着搞钱,一些有价值的事情,只能慢慢地去作。