Android 代码质量管理

前言

“任何一个傻瓜都能写出计算机可以理解的代码,唯有写出人类容易理解的代码,才是优秀的程序员。”

 

 代码不仅仅是人类和计算机沟通的语言,它也是建立在程序员之间的桥梁,两个程序员在沟通的时候,任何富有表达力的言语都不如直接阅读对方一段代码。

代码也是公司的一笔特殊财富,因为它不可能永远被同一个程序员维护,如果代码的可读性很差的话,很可能导致这笔财富无法传承下来,前功尽弃。

具有良好可读性的代码能让功能的扩展和BUG的修复更顺利,增加新功能、修改某个BUG都需要你首先理解代码。

所以,提高代码质量是很有必要的,本文将介绍个人在实践中认为能够提高代码质量的方法,希望对大家有所帮助。

 

 

 

理想和现实

 

 

 

软件缺陷

 

         

             世上本没有BUG

    随着功能的增加,便有了BUG

    老的BUG改了,可能引入新的BUG

 

    事实: 我们的软件,在发布前,其实就已经百病缠身了。

 

 

烂代码的伤害

 

  • 1. 不利于其他同事维护和更新
  • 2. 维护、时间成本的增加
  • 3. 无法传承下去,维护成本比重新开发成本更高。

 

软件发布后的情形:

0、领导:这次发布的版本BUG太多了,客户天天往我手机打电话,你们在搞什么东西?

1、项目经理:加班、加班、加班,都留下来给我加班 (把地给我扫干净!)

2、程序员:丧失工作热情 (我们一个个都是怀揣着梦想进入公司的,结果待了2年,发现并不是这么美好。

我常听到程序员说:哎,我们的代码就是一坨X。X是一种什么样的状态,你们懂的。

都说写代码是艺术,有人说,我怎么感觉像蓝领,没啥技术含量呢?)

3、结果:频繁跳槽 (其实是被动跳槽,代码不敢维护下去了,随便一个小改动都会引来新的问题……于是从一个火坑跳进了另一个更大的火坑。)

 

 

 

发现、修改和预防烂代码

疑问1:我们如何发现烂代码?

 

    多看网上的大神写的代码,看多了自然就有识别的能力了。

疑问2:烂代码要不要改呢?应该怎么改?

 

  • 但愿客户不要发现……
  • 不影响功能,反正用户也看不到,不要改了
  • 时间来不及了,我们下个版本再说吧

 

疑问3:如果烂代码不是先天性的,那是不是可以预防?

     没有好的方式去预防。

 

 

制约程序员编写高质量代码的因素

 

1. 对需求和设计的理解不透彻

2. 对软件业务流程不熟悉

3. 没有开发经验,不知道怎样的代码是好的

4. 对开发工具或开发语言不熟悉

5. 缺少监督体系或不重视质量评估

6. 受情绪因素的影响等因素

7. 其它非代码因素也起着关键作用

    - 对于一些经验丰富的编程人员,他们是知道怎么写代码是最好的,最有效率的,但是为什么他们写出的代码也很烂呢?—— 破窗效应

 

好代码的特性

 

  •  可读性
  • 可维护性
  •  可变更性
  •  正确性

 

 

可读性

重视开发规范

 

  • 1. 代码整洁、类、变量、方法命名规则
  • 2. 代码样式,缩进与换行
  • 3. 多写注释

可维护性

 

  •  方法清晰,容易理解
  • 函数不要超过100行
  •  类不要超过1000行
  • 不要硬编码
  •  定义常量或配置文件修改常用属性
  •  保持代码简洁
  •  函数越短小越好

 

可变更性

 

  • 代码复用,减少冗余。
  • 适当的利用23种设计模式
  •  多人开发可以采用MVP或者其他开发模式。
  •  设计原则

            ① 单一职责,一个类或者一个方法只做一样事情。

            ② 不要面向实现编程,要面向接口编程。

            ③ 抽取封装共同代码,减少代码冗余。

            ④ 代码尽量做到对修改关闭,对扩展开放。

 

重构

    所谓重构(refactoring)是这样一个过程:在不改变代码外在行为的前提下,对代码做出修改。

    重构并不是重写,重构可以改进程序的内部结构,从而提高代码体的可维护性。

    重构的好处:

 

  •             改进代码美观度
  •              快捷寻找BUG
  •             提高开发速度
  •             改进源码可读性
  •             增加程序的可扩展性

 

 

     一般来说有以下三种场景,可以重构: 

  •          添加功能时重构 
  •          修补错误时重构 
  •          复审代码时重构 

 

        项目完成后养成重构代码习惯,不仅能减少以后的维护成本,还能对这次项目的经验进行总结。

    自身的编程能力也能得到相应的提高。

 

 

设计模式

    设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

 

其他提高代码质量的方式

 

  • 代码评审

模板方法-基类封装

   Activity和Fragment应该是Android最常用的组件,对他进行简单的封装对提高代码的简洁性也有很大的帮助。

 

  • BaseActivity :
public abstract class BaseActivity extends FragmentActivity {
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		init();
		findViews();
		initData();
		setListener();
		setting();
	}
	
	/**
	 * 获得上下文
	 * @return Context
	 */
	public Context getContext(){
		return this;
	}
	
	/**
	 * 始化参数
	 */
	public abstract void init();
	/**
	 * 查找所有的控件
	 */
	public abstract void findViews();
	/**
	 * 初始化页面数据
	 */
	public abstract void initData();
	/**
	 * 设置控件的监听事件
	 */
	public abstract void setListener();
	
	/**
	 * 后续参数设置
	 */
	public abstract void setting();

}

 

  • BaseFragment :
public abstract class BaseFragment extends Fragment {


	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
	}

	@Override
	public void onStart() {
		super.onStart();
		init();
		findViews();
		initData();
		setListener();
		setting();
	}

	public Context getContext() {
		return getActivity();
	}

	public abstract void init();

	public abstract void findViews();

	public abstract void initData();

	public abstract void setListener();

	public abstract void setting();

}

代码比较简单,用到了模板设计模式,一个方法只做一样事情,初始化的就只做初始化操作,设置监听的就只设置监听。不管多少个Activity\Fragment都能很好的统一化编码风格,看起来更清晰不乱。

 

Fragment简单管理

下面先看看标准的创建和管理Fragment。

	private void showFragment(){
		FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
		hideFragment(fragmentTransaction);
		if (mFragment1== null) {
			mFragment1 = new MyFragment1(context);
			fragmentTransaction.add(R.id.content, mFragment1);
			fragmentTransaction.commit();
		} else {
			fragmentTransaction.show(mFragment1);
			fragmentTransaction.commit();
		}
	}

每次创建一个Fragment都要复制一边这个方法,代码冗余、不利于维护和更新。

下面封装一下

public class FragmentFactory {

	private FragmentActivity mContext;
	private static FragmentFactory factory = new FragmentFactory();
	//用于存储已创建的Fragment对象
	private Map<String, Fragment> mFragmentMap=new HashMap<>();
	private int mLayoutId;

	private FragmentFactory() {
	}

	public static FragmentFactory getInstance() {
		return factory;
	}

	//layoutId 传入布局文件的id
	public FragmentFactory init(FragmentActivity context,int layoutId) {
		this.mContext = context;
		this.mLayoutId=layoutId;
		return factory;
	}

	public Activity getParentActivity() {
		return mContext;
	}

	
	private <T extends Fragment> Fragment createFragment(Class<T> clazz) {
		Fragment fragment = null;
		try {
			fragment = getFragment(clazz.getName());
			FragmentTransaction fragmentTransaction = mContext.getSupportFragmentManager().beginTransaction();
			hideFragment(fragmentTransaction);
			if (fragment == null) {

				fragment = (Fragment) clazz.newInstance();
				setFragment(fragment);
				fragmentTransaction.add(mLayoutId, fragment);
				fragmentTransaction.commit();
			} else {
				fragmentTransaction.show(fragment);
				fragmentTransaction.commit();
			}
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return fragment;
	}

	private <T extends Fragment> Fragment getFragment(String className) {
		Fragment fragment = mFragmentMap.get(className);
		return fragment;
	}

	private <T extends Fragment> void setFragment(Fragment fragment) throws InstantiationException, IllegalAccessException {
		String className = fragment.getClass().getName();
		mFragmentMap.put(className, fragment);
	}

	private void hideFragment(FragmentTransaction fragmentTransaction) {
		Set<String> keySet = mFragmentMap.keySet();
		for (String key : keySet) {
			Fragment fragment = mFragmentMap.get(key);
			fragmentTransaction.hide(fragment);
		}
		
	}
	
	public <T extends Fragment> T showFragment(Class<T> clazz) {
		return (T) createFragment(clazz);
	}
}

 

调用代码:

 

FragmentFactory mFragmentFactory = FragmentFactory.getInstance().init(this, R.id.fl_content);
mFragmentFactory.showFragment(MyFragment1.class);
mFragmentFactory.showFragment(MyFragment2.class);

上面的封装用到了泛型、工厂、单例等知识。只需要在Activity初始化一次对象就可以一行代码管理Fragment了,想显示哪个页面就传入对应的Fragment的class。

 

简单通用的适配器

ListView是Android最常用的一个组件,优化Litsview那就是必不可少的工作了。

用Listview最痛苦的就是写BaseAdapter的getView()方法,一遍又一遍的写,大部分代码都是重复冗余,但又不得不写。下面来抽取冗余的代码封装起来。

public abstract class CommonAdapter<T> extends BaseAdapter {
    //需要显示的数据,List中的类型为泛型,因为不知道用户的封装Bean
    private List<T> mDatas;
    
    private Context mContext;
    //布局文件Id
    private int mLayoutId;
    public CommonAdapter(Context context,List<T> data,int layoutId) {
        mDatas = data;
        mContext = context;
        mLayoutId = layoutId;
    }
    @Override
    public int getCount() {
        return mDatas.size();
    }

    @Override
    public Object getItem(int position) {
        return mDatas.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = ViewHolder.getHolder(mContext,convertView, parent, mLayoutId);
        setDatas(holder,getItem(position));
        return holder.getConvertView();
    }

    /**
     * 为各个item中的控件设置数据
     * @param holder   ViewHolder
     * @param object  从集合中所取的一个对象
     */
    public abstract void setDatas(ViewHolder holder, Object object);
}
public class ViewHolder {
    private View mConvertView;
    //用来存布局中的各个组件,以键值对形式
    private HashMap<Integer,View> mViews = new HashMap<>();
    //ViewHolder构造函数,只有当convertView为空的时候才创建
    public ViewHolder(Context context,View convertView, ViewGroup parent, int layouId) {
        convertView = LayoutInflater.from(context).inflate(layouId,parent,false);
        convertView.setTag(this);       //将其setTag()
        mConvertView = convertView;
    }
    //返回一个ViewHolder对象
    public static ViewHolder getHolder(Context context, View convertView, ViewGroup parent, int layoutId) {
        if (convertView == null) {
            return new ViewHolder(context,convertView,parent,layoutId);
        }else {
            return (ViewHolder) convertView.getTag();
        }
    }
    //返回一个View的子类对象,因为不确定用户布局有什么组件,相当于findViewById
    //这里返回一个泛型,也可以返回一个View或Object
    public <T extends View>T getView(int resId) {
        View view = mViews.get(resId);  //从集合中取出这个组件
        if (view == null) {         //如果为空,说明为第一屏
            view = mConvertView.findViewById(resId);    //从convertView中找
            mViews.put(resId,view);     
        }
        return (T) view;
    }

    public View getConvertView() {
        return mConvertView;
    }
}

调用代码:

public class MyAdapter extends CommonAdapter<Bean> {
    public MyAdapter(Context context, List<Bean> data, int layoutId) {
        super(context, data, layoutId);
    }
    @Override
    public void setDatas(ViewHolder holder, Object object) {
        Bean bean = (Bean) object;
        ((TextView)holder.getView(R.id.title_Tv)).setText(bean.getTitle());
        ((TextView)holder.getView(R.id.desc_Tv)).setText(bean.getDesc());
        ((TextView)holder.getView(R.id.time_Tv)).setText(bean.getTime());
        ((TextView)holder.getView(R.id.phone_Tv)).setText(bean.getPhone());
    }
}
    List<Bean> data=new ArrayList<>();
    Bean bean=new Bean("标题1", "内容1", "时间1", "18300000000");
    Bean bean2=new Bean("标题2", "内容2", "时间2", "18300000000");
    data.add(bean);
    data.add(bean2);
    listView.setAdapter(new MyAdapter(context, data, R.layout.listview_item));

注释写的很清楚了,就不多说了。

 

自定义组合控,布局模块化

    正常的项目开发中肯定有很多布局冗余例如下面图红框中的设置和导航。

 

                            图1                                                        图2

很多人会把这些布局文件一遍又一遍的复制,只修改其中的ID、字符串等,其他部分几乎一模一样,造成布局文件代码特别多。

最要命的不是这个,而且把所有的逻辑写在Activity\Fragment里,造成Activity\Fragment特别的庞大,真正实现一坨X代码。

我觉得应该把公用的布局单独抽取出来到一个xml里,再用一个GroupView去处理这些逻辑和业务,减少activity\Fragment的负担。

代码就不贴了,自己去源码demo里查看ParamSwitchView,这个View是图1的一个Item,封装了布局和所需要的遥控按键左右切换数据的逻辑。

 

面向接口编程

   

    面向接口编程的意思是指在面向对象的系统中所有的类或者模块之间的交互是由接口完成的。

    父类的引用指向子类对象,指向不同的子类对象,产生不同的行为:   

         父 a =new 子A;

 

有很多童靴在项目开发中经常更变业务,例如:定制化系统应用,底层的接口在不同型号的TV\手机上都有可能不一样。

这时候把这些底层接口单独封装在一个类进行管理,在平台发生改变的时候只需要改变实现。

定义接口类统一化管理方法

public interface IManager {
	
	void setBackLight(int value);
	void setPictureMode(int mode);

}

实现类 1

public class HuaWeiManager implements IManager {
	
	@Override
	public void setBackLight(int value) {
		HuaWei.savaBackLight(value);
	}

	@Override
	public void setPictureMode(int mode) {
		HuaWei.setPictureMode(mode);
	}

}HuaWei.savaBackLight(value);
	}

	@Override
	public void setPictureMode(int mode) {
		HuaWei.setPictureMode(mode);
	}

}
 

假如现在业务需求是华为的定制系统,只需要调用华为的子类

IManager iManager=new HuaWeiManager();
iManager.setBackLight(100);

如果业务需求转变成小米,那么只需要创建一个类进行实现

实现类 2

public class XiaoMiManager implements IManager {
	
	@Override
	public void setBackLight(int value) {
		XiaoMi.savaBackLight(value);
	}

	@Override
	public void setPictureMode(int mode) {
		XiaoMi.setPictureMode(mode);
	}

}

调用代码里只需要把HuaWeiManager改成XiaoMiManager就能适配其他机型了。

	//IManager iManager=new HuaWeiManager();
	IManager iManager=new XiaoMiManager();
	iManager.setBackLight(100);

在这里只是灌输一个编码思维,实际开发中突发情况比较多,并不一定全部适用。

在编码之前一定要花一点点时间简单构思和组织一下代码,不要想到什么写什么。

 

注重工具类的封装

    我们正常的开发中经常用到很多不需要在逻辑层编写的方法,我们就可以单独的把他抽取出来放在单独的类里面去单独管理。

     例如:Toast 、SharePreference、获取时间、系统版本、网络、MD5等等。。。。

   这些东西都可以单独的封装和管理,减少逻辑层的代码,并且也可以让其他逻辑层调用。

坏习惯

        有些人喜欢把定义个Tools这样的工具类,里面存放着所有的工具方法。

       1. 网络、Toast、状态、时间等等全部都用一个类去管理,这样造成的后果就是后期不方便维护和不利于更新,代码看起来杂乱无章。

        2. 把一些公共的方法直接在逻辑层构建,其他地方需要就直接复制一份过去。

            或者有其他相同的比较类似的方法没有进行封装,在其他地方直接复制过去只修改其他一行的代码。

好习惯

    1. 把这些tools单独创建各种各样的tools去存放这些方法,Toast只存Toast相关的,网络只存网络相关的,避免交杂在一起。也符合设计原则之一的:单一原则。

    2. 类似的方法独立抽取出来,用传参flag标记去区分应用场景。

 

源码里收藏了一些常用的工具类分享给大家。

 

 

 

MVP分层架构

    去年写了一篇关于它的文章,大家可以看看。能够让代码变得异常的清晰。

    https://blog.csdn.net/u012999130/article/details/70173245

 上面说了那么多,只是一些基本的理论,下面是分享一点平时开发中比较实在的东西。

点击打开链接

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值