我是如何在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萬+碼農成長充電第一站,陪有夢想的你一起成長。


分享到:


相關文章: