preface

High energy warning: this article is a little long, it is recommended to see again after collection.

Here’s how one lone hunk has single-handedly reconstructed an entire mountain of shit.

This handsome man has been wandering, who should I write this article to? Is it a coder who only cares about writing? No, no, no, no, no, no, no, no, no, no, no, no, no, no, no, no, no, no, no, no, no, no, no, no, no, no, no.

So simply write refactoring experience, share refactoring ideas, let those who consciously improve in this aspect of the handsome men and women, less detours!

Thank you for your trust and support. In this reconstruction, the handsome boy sold and took the lead in using a certain architecture within the department, and completed the reconstruction of core modules of 60 classes within 5 days. (Don’t panic, the architecture is open source on GitHub, linked at the end of this article.)

The following text.

How does code get worse and worse?

How often have you heard your colleagues make fun of themselves by saying, “At first you try to write well, but then it gets worse and worse?”

The worse the code is written, is it really a no-clue, unintervenable magic metaphysics?

Let’s take a quick look at how the code was written in the project before refactoring.

  protected void initView(a) {
    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()) {
          switch (position) {
            case 0:
            case 1:
              mViewModel.removeAllArrows();
              if(mAttachmentFragment ! =null) {
                mAttachmentFragment.hideClickHighLight(ALBUM_ALL);
              }
              break;
            case 2:
              if(mAttachmentFragment ! =null) {
                mAttachmentFragment.initAttachTitle();
              }
              mViewModel.showAllArrows();
              break;
            default:
              break; }}else {
          switch (position) {
            case 0:
            case 1:
            case 2:
              mViewModel.removeAllArrows();
              //hideBottomLayout();
              if(mAttachmentFragment ! =null) {
                mAttachmentFragment.hideClickHighLight(ALBUM_ALL);
              }
              break;
            case 3:
              if(mAttachmentFragment ! =null) {
                mAttachmentFragment.initAttachTitle();
              }
              mViewModel.showAllArrows();
              break;
            default:
              break; }}}@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(newRefreshItemEventBus( 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 {
      switch (position) {
        case 0:
          mViewModel.removeAllArrows();
          if(mAttachmentFragment ! =null) {
            mAttachmentFragment.hideClickHighLight(ALBUM_ALL);
          }
          break;
        case 1:
          mViewModel.showAllArrows();
          break;
        default:
          break; }}}/** * viewPager adapter */
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
            );
          }
          returnmAttachmentFragment; }}else {
      switch (position) {
        case 0:
          if (mXXXTuBanPicFragment == null) {
            mXXXTuBanPicFragment = XXXTuBanPicFragment.newInstance(
                    mViewModel.getUniqueCode(),
                    mViewModel.getXXXTouchManger()
            );
          }
          return mXXXTuBanPicFragment;
        case 1:
          if (mAttributeFragment == null) { mAttributeFragment = XXXAttributeFragment.newInstance( mViewModel.getUniqueCode(), mViewModel.getXXXTouchManger() );  }return mAttributeFragment;
        case 2:
          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
            );
          }
          returnmAttachmentFragment; }}}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
            );
          }
          returnmAttachmentFragment; }}else {
      switch (position) {
        case 0:
          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
            );
          }
          returnmAttachmentFragment; }}}@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()) {
      switch (position) {
        case 0:
          mXXXTuBanPicFragment = (XXXTuBanPicFragment) object;
          break;
        case 1:
          mRecordFragment = (XXXRecordFragment) object;
          break;
        default:
          mAttachmentFragment = (XXXAttachmentFragment) object;
          break;
      }
      return object;
    } 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;
      }
      returnobject; }}@Override
  public int getCount(a) {
    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; }}Copy the code

(The module class name has been replaced with “XXX” to protect privacy)

As you can see, the home page currently serves three regions, each of which has custom requirements for the presentation of sub-pages.

If else switch if else switch, only care about the implementation of the code farmers write this.

50 lines for one region, what about 10 regions? Company leaders put words to support the country’s 100 towns and villages! What about the 100 districts??

Abstract, conforming to the “open closed principle”

These are code farmers who have no sense of abstraction.

They hear “abstract”, just like I don’t like exercise when I hear parents, friends advise me to “fitness” passive. (smile)

Just as I didn’t really understand what fitness was all about, they thought abstractions were “in one ear and out the other”.

“100 Regions” is the natural abstraction and customization of the factory model, which is obvious and unquestionable.

The refactored code has a warning header.

/ * Friendly tips: this class is coated with anti-corrosion drugs, do not touch, do not touch, do not touch! * < p > * custom functions, including the layout of the characteristics, inheritance in writing AbstractDetailChildFragmentManager alone, please! * /public class XXXDetailFragment extends BaseFragment implements IResponse {
    protected void initView(a) {
        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 adapter */
    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(a) {
            returnmDetailChildFragmentManager.getCount(); }}}Copy the code

How does the code get so messy?

Many people have heard of “code coupling” and “decoupling,” but you’re probably the only one who really understands what it is

Because even if you don’t know it, you’re about to see a handsome man take you hand in hand and decouple you

Let’s take a look at the pre-refactoring code!

public interface XXXListNavigator {

    void updateRecyclerView(a);

    void showProgressDialog(a);

    void dismissProgressDialog(a);

    void updateListView(a);

    void updateLayerWrapperList(List<LayerWrapper> list);

    boolean isAnimationFinish(a);

    void resetCount(a);

}

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(), "Operation successful");
                        clearData();
                        loadData(true.true);
                        if (null != mNavigator) {
                            mNavigator.dismissProgressDialog();
                        }
                    }

                    @Override
                    public void onError(a) {
                        ToastUtils.showShort(getApplicationContext(), "Operation failed");
                        if (null! = mNavigator) { mNavigator.dismissProgressDialog(); }}}); }}Copy the code

As you can see, the UI overexposes the “process API on which the UI logic depends” and directly interferes with the UI logic in the business, which is typical of MVP writing and leads to coupling. When UI requirements change, both View and Presenter writers are affected.

Moreover, too many responsibilities lead to too many dependencies, and presenters tend to become bloated with too many dependencies: driven by the “broken window effect”, other programmers will continue to write without thinking because there is already a dependency.

What exactly is decoupling

Decoupling is coding in accordance with engineering design and design pattern principles.

The nature of decoupling, I’ll just say it once:

Clear boundaries, clear boundaries, clear boundaries.

Follow the single responsibility principle: the RESPONSIBILITY of the UI is limited to “presentation,” sending requests and processing UI logic. The business’s responsibility is limited to “providing data,” that is, receiving requests, processing business logic, and responding to resulting data.

It follows the dependency inversion principle and the minimum knowledge principle: the UI doesn’t need to know how the data was turned around, it just sends the request and digests the UI logic internally when it gets the resulting data. The business simply processes the data and responds to the DATA to the UI; it doesn’t need to know how the UI uses the data, let alone intervene.

To sum up, neither the UI nor the business should overexpose the internal logic API and be controlled by humans; they should only expose the request API in response to external requests. The process logic should only be digested independently within itself.

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(), "Operation failed");
                }
                dismissProgressDialog();
                break;
		    case XXXDataResultCode.XXX_LIST_FRAGMENT_REFRESH_PATTERN:
		        ...
		        break;
		    default:}}}Copy the code

What are the benefits of decoupling?

Ford has the best word for decoupling.

More than 100 years ago, Ford invented the world’s first assembly line, which allowed workers to divide and focus on their areas with clear boundaries.

It used to take 700 hours to assemble a car, but after the assembly line division, the average is 12.5 hours, which improves the production efficiency by nearly 60 times!

The same goes for software engineering.

Because UI and business responsibilities have clear boundaries and communicate with each other through interfaces, there is a real division of labor between UI and business writers.

The person who writes the UI is not interrupted by business writing, he can write his OWN UI without stopping. The person who writes the business, again without interruption, can focus on optimizing business logic, data structures, and algorithms.

People who write UI and business can implement their own interfaces and do unit tests independently without having to rely on or wait for each other’s implementations.

Finally, in the case of clear responsibility boundaries, even if THE UI writes 100 UI logic, it is still UI, and even if the business writes 100 business, it is still business, pure species, so there is no clutter, and we can use the “interface isolation principle” to continue the division of work!

.

conclusion

To sum up, this paper introduces two refactoring ideas:

1. Follow the open and close principle and abstract the customization function.

2. Follow the principle of single responsibility, minimum knowledge and inversion of dependence to make responsibility boundaries clear and prevent code coupling.

After reading this article, if you feel that you have gained and inspired, please feel free to give a thumbs-up, your thumbs-up is the biggest support for me!

The VIABUS architecture used in this project reconstruction, which conforms to the principles of design pattern, has been open source on GitHub: GitHub: KunMinX/ Android-Viabus-Architecture

More articles

What it’s like to have an open source project sold for over 10 million dollars as a course?

Jetpack MVVM is a breath of fresh air!

The Jetpack | let a person shine at the moment of Java Kotlin 1:1 comparison sample”