我是如何在5 天内,完成 60 个类的核心模块的重构

代码是如何越写越烂的?

你是否经常听同事自嘲,“开始还想好好写,不知怎滴,后面越写越烂”?

代码越写越烂,果真是个没有端倪、无法干预的魔咒玄学吗?

我是如何在5 天内,完成 60 个类的核心模块的重构

让我们来快速浏览一下 重构前 项目里的代码是怎么写的。

<code>protected void initView() {
PagerAdapter pagerAdapter = new PagerAdapter();
viewPagerFix.setOffscreenPageLimit(4);
viewPagerFix.setAdapter(pagerAdapter);
mFragmentBinding.tabLayout.setTabData(pagerAdapter.titles);
mFragmentBinding.tabLayout.setOnTabSelectListener(new OnTabSelectListener() {
@Override
public void onTabSelect(int position) {
viewPagerFix.setCurrentItem(position);
}

@Override
public void onTabReselect(int position) {

}
});
viewPagerFix.addOnPageChangeListener(new ViewPagerFix.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
KeyboardUtils.hideSoftInput(getActivity());
}

@Override
public void onPageSelected(int position) {
mFragmentBinding.tabLayout.setCurrentTab(position);
if (mViewModel.getXXXDetailTouchManager().isZZBG()) {

zzbgPageSelected(position);

} else if (mViewModel.getXXXDetailTouchManager().isYBJZ()) {
...
} else {
...
}
}

@Override
public void onPageScrollStateChanged(int state) {

}
});
viewPagerFix.setCurrentItem(0);
mFragmentBinding.headContainer.getTitleView().setOnClickListener(new View.OnClickListener() {
@Override

public void onClick(View v) {
if (mViewModel.getXXXDetailTouchManager().isZZBG()) {
return;
}

mViewModel.changeWyhcrwMajorState();
EventBus.getDefault().post(new RefreshItemEventBus(
mViewModel.getXXXDetailTouchManager().getCurrentWyhcrw()));
}
});
}

private void zzbgPageSelected(int position) {

if (mScreenNum == 3) {

switch (position) {
case 0:
case 1:
mViewModel.removeAllArrows();

if (mAttachmentFragment != null) {
mAttachmentFragment.hideClickHighLight(ALBUM_ALL);
}
break;
case 2:
mViewModel.showAllArrows();

break;
default:
break;
}

} else {
...
}
;

}



/**
* viewPager适配器
*/
private class PagerAdapter extends FragmentPagerAdapter {

String[] titles;

PagerAdapter() {

super(getChildFragmentManager());
if (mViewModel.getXXXDetailTouchManager().isZZBG()) {

if (mScreenNum == 3) {

titles = getResources().getStringArray(R.array.XXX_detail_tabs_for_no_tbjt);

} else {
titles = getResources().getStringArray(R.array.XXX_detail_tabs_for_zzbg);

}

} else if (mViewModel.getXXXDetailTouchManager().isYBJZ()) {
titles = getResources().getStringArray(R.array.XXX_detail_tabs_for_ybjz);
} else {
titles = getResources().getStringArray(R.array.XXX_detail_tabs);
}
}

@Override
public Fragment getItem(int position) {
if (mViewModel.getXXXDetailTouchManager().isZZBG()) {

return zzbgGetItem(position);

} else if (mViewModel.getXXXDetailTouchManager().isYBJZ()) {
switch (position) {
case 0:
if (mXXXTuBanPicFragment == null) {
mXXXTuBanPicFragment = XXXTuBanPicFragment.newInstance(
mViewModel.getUniqueCode(),
mViewModel.getXXXTouchManger()
);
}
return mXXXTuBanPicFragment;
case 1:
if (mRecordFragment == null) {
mRecordFragment = XXXRecordFragment.newInstance(mViewModel.getXXXDetailTouchManager());
}
return mRecordFragment;

default:
if (mAttachmentFragment == null) {
mAttachmentFragment = XXXAttachmentFragment.newInstance(
mViewModel.getAttachments(),
mViewModel.getOriginalAttachments(),
mViewModel.getUniqueCode(),
mViewModel.getXXXTouchManger(),
XXXDetailFragment.this
);

}
return mAttachmentFragment;
}
} else {
...
}
}

private Fragment zzbgGetItem(int position) {

if (mScreenNum == 3) {

switch (position) {
case 0:
if (mAttributeFragment == null) {
mAttributeFragment = XXXAttributeFragment.newInstance(
mViewModel.getUniqueCode(),
mViewModel.getXXXTouchManger()
);
}
return mAttributeFragment;
case 1:
if (mRecordFragment == null) {
mRecordFragment = XXXRecordFragment.newInstance(
mViewModel.getXXXDetailTouchManager());
}
return mRecordFragment;
default:
if (mAttachmentFragment == null) {
mAttachmentFragment = XXXAttachmentFragment.newInstance(
mViewModel.getAttachments(),
mViewModel.getOriginalAttachments(),
mViewModel.getUniqueCode(),
mViewModel.getXXXTouchManger(),
XXXDetailFragment.this
);
}
return mAttachmentFragment;
}

} else {
....
}

}

@Override
public Object instantiateItem(ViewGroup container, int position) {
Object object = super.instantiateItem(container, position);
if (mViewModel.getXXXDetailTouchManager().isZZBG()) {

if (mScreenNum == 3) {

switch (position) {
case 0:
mAttributeFragment = (XXXAttributeFragment) object;
break;
case 1:
mRecordFragment = (XXXRecordFragment) object;
break;
default:
mAttachmentFragment = (XXXAttachmentFragment) object;
break;
}

} else {
switch (position) {
case 0:
mRecordFragment = (XXXRecordFragment) object;
break;
default:
mAttachmentFragment = (XXXAttachmentFragment) object;
break;
}
}

return object;
} else if (mViewModel.getXXXDetailTouchManager().isYBJZ()) {
...
} else {
switch (position) {
case 0:
mXXXTuBanPicFragment = (XXXTuBanPicFragment) object;
break;
case 1:
mAttributeFragment = (XXXAttributeFragment) object;
break;
case 2:
mRecordFragment = (XXXRecordFragment) object;
break;
default:
mAttachmentFragment = (XXXAttachmentFragment) object;
break;
}
return object;
}
}

@Override
public int getCount() {
if (mViewModel != null) {

if (mViewModel.getXXXDetailTouchManager().isZZBG()) {
if (mScreenNum == 3) {
return 3;
}
return 2;
}
if (mViewModel.getXXXDetailTouchManager().isYBJZ()) {
return 3;
} else {
return 4;
}
}
return 0;
}

}
/<code>

(为保护隐私,模块类名已替换为“XXX”)

可以看到,该主页目前服务于 3 个地区,每个地区对子页面的展示都有定制需求。

if else switch if else switch,只在乎功能实现的码农就是这么写的。

一个地区 50 行,那要是 10 个地区呢?公司领导放话要支持全国 100 个乡镇地区!那 100 个地区呢???

我是如何在5 天内,完成 60 个类的核心模块的重构


抽象,顺应的是“开闭原则”

这是一帮对“抽象”无感的码农。

他们听到“抽象”,就像不爱锻炼的我听到父母、朋友劝我“健身”一样被动。(笑)

正如我并不真的理解健身的意义所在,他们也当抽象是“耳边风”。

“100 个地区”这种,天然的就是用工厂模式来抽象和定制,这原本是一目了然、毫无疑问的事。

重构后的代码,主页抬头特意标注了警告。

<code>/
* 友情提示:本类涂有防腐药品,切勿触碰,切勿触碰,切勿触碰!
*


* 地区定制功能,包括特色的布局等,请继承于 AbstractDetailChildFragmentManager 单独编写!
*/

public class XXXDetailFragment extends BaseFragment implements IResponse {
protected void initView() {
initViewPagerManager();
PagerAdapter pagerAdapter = new PagerAdapter();
viewPagerFix.setOffscreenPageLimit(4);
viewPagerFix.setAdapter(pagerAdapter);


mFragmentBinding.tabLayout.setTabData(pagerAdapter.titles);
mFragmentBinding.tabLayout.setOnTabSelectListener(new OnTabSelectListener() {
@Override
public void onTabSelect(int position) {
viewPagerFix.setCurrentItem(position);
}

@Override
public void onTabReselect(int position) {

}
});
viewPagerFix.addOnPageChangeListener(new ViewPagerFix.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
KeyboardUtils.hideSoftInput(getActivity());
}

@Override
public void onPageSelected(int position) {
mFragmentBinding.tabLayout.setCurrentTab(position);
mDetailChildFragmentManager.onPageSelected(position);
}

@Override
public void onPageScrollStateChanged(int state) {

}
});
}

/**
* viewPager适配器
*/
private class PagerAdapter extends FragmentPagerAdapter {

String[] titles;

PagerAdapter() {
super(getChildFragmentManager());
titles = mDetailChildFragmentManager.getTitles();
}

@Override
public Fragment getItem(int position) {
return mDetailChildFragmentManager.getItem(position);
}

@Override
public Object instantiateItem(ViewGroup container, int position) {

Object object = super.instantiateItem(container, position);
return mDetailChildFragmentManager.instantiateItem(container, position, object);
}

@Override
public int getCount() {
return mDetailChildFragmentManager.getCount();
}
}
}

/<code>

代码是如何剪不断理还乱的?

听说过“代码耦合”和“解耦”的人很多,但真正理解这是怎么一回事的,恐怕只有你 ~

因为哪怕你不知,你也即将见证一位帅哥如何手把手带你解耦 ~

我们先来看下重构前的代码!

<code>public interface XXXListNavigator {

void updateRecyclerView();

void showProgressDialog();

void dismissProgressDialog();

void updateListView();

void updateLayerWrapperList(List<layerwrapper> list);

boolean isAnimationFinish();

void resetCount();

}

public class XXXListViewModel extends BaseViewModel {
public void multiAddOrRemove(ArrayList<string> bsms, boolean isAdd) {
if (null != mNavigator) {
mNavigator.showProgressDialog();
}
if (null == mMultiAddOrRemoveUseCase) {
mMultiAddOrRemoveUseCase = new MultiAddOrRemoveUseCase();
}
mUseCaseHandler.execute(mMultiAddOrRemoveUseCase, new MultiAddOrRemoveUseCase.RequestValues(isAdd, bsms,
mLayerWrapperObservableField.get()),
new UseCase.UseCaseCallback<multiaddorremoveusecase.responsevalue>() {
@Override
public void onSuccess(MultiAddOrRemoveUseCase.ResponseValue response) {
ToastUtils.showShort(getApplicationContext(), "操作成功");
clearData();
loadData(true, true);
if (null != mNavigator) {
mNavigator.dismissProgressDialog();
}
}

@Override
public void onError() {
ToastUtils.showShort(getApplicationContext(), "操作失败");
if (null != mNavigator) {
mNavigator.dismissProgressDialog();
}
}
});
}
}
/<multiaddorremoveusecase.responsevalue>/<string>/<layerwrapper>/<code>

可以看到,UI 过度暴露了“处理 UI 逻辑所依赖的过程 API”,并在业务中直接干预了 UI 逻辑,这是典型的 MVP 写法,这造成了耦合。一旦 UI 的需求有变动,View 和 Presenter 的编写者都会受到牵连。

而且,职责过多造成了依赖过多,这个 Presenter 会因为过多的依赖,而越写越臃肿:受“破窗效应”的驱使,别的码农会因为此处已经有某个依赖,而不假思索的接着往下写。

到底怎样才算解耦

所谓解耦,是符合工程设计、符合设计模式原则的编码。

解耦的本质,我只说一遍:

职责边界明确,职责边界明确,职责边界明确。

我是如何在5 天内,完成 60 个类的核心模块的重构

viabus_flow_flow.png


符合单一职责原则:
UI 的职责仅限于“展示”,也就是发送请求、处理 UI 逻辑。业务的职责仅限于“提供数据”,也就是接收请求、处理业务逻辑、响应结果数据。

符合依赖倒置原则、最小知识原则:UI 不需要知道数据是经过怎样的周转得来的,它只需发送请求,并在拿到结果数据后,自己内部消化 UI 逻辑。业务只需处理数据并响应数据给 UI,它不需要知道 UI 会怎样使用数据,更无权干预。

综上,无论是 UI 还是业务,都不应过度暴露内部逻辑 API 而受控于人,它们应只暴露请求 API,来响应外部的请求。过程逻辑应只在自己内部独立消化。

<code>public class XXXListBusinessProxy extends BaseBusiness<xxxbus> implements IXXXListFragmentRequest {

@Override
public void multiAddOrRemove(final XXXListDTO dto) {
handleRequest((e) -> {
...
if (TextUtils.isEmpty(existBsms)) {
sendMessage(e, new Result(XXXDataResultCode.XXX_LIST_FRAGMENT_MULTI_ADD_OR_REMOVE, false));
} else {
wyhcJgDBManager.insertAllTaskOfMine(existBsms, layersConfig);
sendMessage(e, new Result(XXXDataResultCode.XXX_LIST_FRAGMENT_MULTI_ADD_OR_REMOVE, true));
}
return null;

});
}

@Override
public void refreshPatternOfXXXList(final XXXListDTO dto) {
handleRequest((e) -> {
...
count.setMyXXXCount(wyhcJgDBManager.getMyXXXPatternCount());
return new Result(XXXDataResultCode.XXX_LIST_FRAGMENT_REFRESH_COUNT, count);
});
}

@Override
public void changeXXXPatternOfMine(final XXXListDTO dto) {
handleRequest((e) -> {
if (toMine) {
...
} else {
...
sendMessage(e, new Result(XXXDataResultCode.XXX_LIST_FRAGMENT_GET_ALL_PATTERN_OF_MINE, count));
}
return null;
});
}
}



public class XXXListFragment extends BaseFragment implements IResponse {

XXXBus.XXX().queryList(mDto);

XXXBus.XXX().multiAddOrRemove(mDto);

XXXBus.XXX().queryPattern(mDto);

...

@Override
public void onResult(Result testResult) {
String code = (String) testResult.getResultCode();
switch (code) {
case XXXDataResultCode.XXX_LIST_FRAGMENT_REFRESH_LIST:
updateRecyclerView((List<wyhcrw>) testResult.getResultObject());
if (isNeedUpdateCount()) {
...
} else {
finishLoading();
}
break;

case XXXDataResultCode.XXX_LIST_FRAGMENT_MULTI_ADD_OR_REMOVE:
if ((boolean) testResult.getResultObject()) {
loadData(true, true);
} else {
ToastUtils.showShort(getContext(), "操作失败");
}
dismissProgressDialog();
break;
case XXXDataResultCode.XXX_LIST_FRAGMENT_REFRESH_PATTERN:
...
break;
default:
}
}
}
/<wyhcrw>/<xxxbus>/<code>

解耦有什么好处?

解耦的好处,福特最有话语权。

100 多年前,福特发明了世界上第一条流水线,让工人职责边界明确,从而得以分工和专注各自领域。

原先装配一辆车需 700 小时,通过流水线分工后,平均一辆 12.5 小时,这使得生产效率提升了近 60 倍!

我是如何在5 天内,完成 60 个类的核心模块的重构

软件工程同理。

由于 UI 和业务职责边界明确,且相互通过接口通信,使得 UI 和业务的编写者能够真正的分工。

写 UI 的人,不会被业务的编写打断,他可以一气呵成的写自己的 UI。写业务的人,同样不会被打断,他可以专注于业务逻辑、数据结构和算法的优化。

写 UI 和写业务的人,都可以自己实现接口,去独立的完成单元测试,完全不必依赖和等候对方的实现。

最后,在职责边界明确的情况下,UI 就算写 100 个 UI 逻辑,那也是 UI,业务就算写 100 个业务,那也是业务,纯种,所以不会杂乱,何况我们还可以借助“接口隔离原则”继续往下分工!

总结

综上,本文介绍了两个重构思路:

1.顺应开闭原则,对定制化功能进行抽象。

2.顺应单一职责、最小知识、依赖倒置原则,让职责边界明确,防止代码耦合。

看完这篇文章,如你觉得有所收获和启发,请不吝点赞,你的点赞就是对我最大的支持!

本次项目重构用到的,符合设计模式原则的 viabus 架构,已在 GitHub 开源:GitHub:KunMinX/android-viabus-architecture

原文作者:KunMinX,经授权原创发布,部分真实感受,希望对大家有帮助,在工作写出更好扩展和可维护的代码。

我是如何在5 天内,完成 60 个类的核心模块的重构

欢迎关注我的微信公众号「码农突围」,分享Python、Java、大数据、机器学习、人工智能等技术,关注码农技术提升•职场突围•思维跃迁,20万+码农成长充电第一站,陪有梦想的你一起成长。


分享到:


相關文章: