How do I complete the refactoring of core modules of 60 classes in 5 days

How does the code get worse?

Do you often hear colleagues laugh at themselves, "at first, you still want to write well. Somehow, the more you write, the worse you will get?"?

The more the code is written, the worse it will be. Is it really a magic metaphysics without clue or intervention? Let's take a quick look at how the code in the project was written before refactoring.

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 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
                            );
                        }
                        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;
        }

    }

(to protect privacy, the module class name has been replaced with "XXX")

It can be seen that the homepage currently serves three regions, and each region has customized requirements for the display of sub pages.

if else switch if else switch, this is what the coder who only cares about the function implementation writes.

One area is 50 lines. What about 10 areas? The leaders of the company said that they should support 100 villages and towns in China! What about 100 districts??? Abstract, conforming to the "opening and closing principle"

This is a group of code farmers who have no sense of abstraction.

They hear "abstraction", just as I hear my parents and friends advise me to "keep fit" when I don't like exercise. (laughter)

Just as I don't really understand the meaning of fitness, they should also abstract it as "ear wind".

The "100 regions" are naturally abstracted and customized with the factory mode, which is clear and undoubted.

After the reconstruction of the code, the header of the home page is specially marked with a warning.

/
 * Friendly tip: this kind of coating has antiseptic drugs, do not touch, do not touch, do not touch!
 * <p>
 * Regional customization functions, including the layout of features, etc., please inherit from AbstractDetailChildFragmentManager Write separately!
 */

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 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() {
            return mDetailChildFragmentManager.getCount();
        }
    }
}

How is the code cut continuously and disorderly?

There are many people who have heard of "code coupling" and "decoupling", but I'm afraid you are the only one who really understands how it is~

Because even if you don't know, you're about to see a handsome guy decoupling you with his hands~

Let's take a look at the code before refactoring!

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

                    @Override
                    public void onError() {
                        ToastUtils.showShort(getApplicationContext(), "operation failed");
                        if (null != mNavigator) {
                            mNavigator.dismissProgressDialog();
                        }
                    }
                });
    }
}

It can be seen that the UI overexposed the "process API on which to process the UI logic", and directly intervened in the UI logic in the business. This is a typical MVP writing method, which caused coupling. Once the requirements of the UI change, the writers of View and Presenter will be involved.

Moreover, too many responsibilities lead to too much dependence. This Presenter will become more and more bloated because of too much dependence: driven by the "broken window effect", other farmers' associations will continue to write without hesitation because there is already a dependency here.

How to decouple

The so-called decoupling is a code that conforms to the principles of engineering design and design pattern.

The essence of decoupling, I will only say once:

The responsibility boundary is clear, the responsibility boundary is clear, and the responsibility boundary is clear. In line with the principle of single responsibility: The responsibility of UI is limited to "display", that is, sending requests and processing UI logic. The responsibility of the business is limited to "providing data", that is, receiving requests, processing business logic, and responding to result data.

According to the principle of Dependence Inversion and minimum knowledge: The UI doesn't need to know how the data is turned around. It just needs to send a request and digest the UI logic internally after getting the result data. The business only needs to process the data and respond to the data to the UI. It doesn't need to know how the UI will use the data, let alone intervene.

To sum up, neither the UI nor the business should over expose the internal logic API and be controlled by people. They should only expose the request API to respond 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:
        }
    }
}

What are the benefits of decoupling?

Ford has the most say in the benefits of decoupling.

More than 100 years ago, Ford invented the world's first assembly line, which defined the boundaries of workers' responsibilities, so as to divide the work and focus on their respective fields.

It took 700 hours to assemble a car. After the assembly line division, the average car was 12.5 hours, which increased the production efficiency by nearly 60 times! The same goes for software engineering.

Because the boundary between UI and business responsibilities is clear, and they communicate with each other through interfaces, the writers of UI and business can really divide the work.

The person who writes UI will not be interrupted by the writing of business. He can write his UI in one go. The person who writes business will not be interrupted. He can focus on the optimization of business logic, data structure and algorithm.

People who write UI and business can implement the interface by themselves and complete unit test independently without relying on and waiting for the other party's implementation.

Finally, when the responsibility boundary is clear, even if the UI writes 100 UI logics, that is also the UI. Even if the business writes 100 businesses, that is also the business, pure species, so it will not be messy. Besides, we can continue to work with the help of the "interface isolation principle"!

summary

In summary, this paper introduces two reconstruction ideas:

1. Conform to the principle of opening and closing, abstract the customized function.

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

After reading this article, if you feel that you have gained and inspired, please don't hesitate to praise. Your praise is my greatest support!

The viabus architecture used in the reconstruction of this project, which conforms to the design pattern principle, has been open-source in GitHub: GitHub: KunMinX/android-viabus-architecture

Original author: KunMinX, authorized original release, part of the real feelings, hope to help you, write better extended and maintainable code in the work.

Welcome to my WeChat official account, "code breakout", sharing technology like Python, Java, big data, machine learning, AI, etc., focusing on code farming technology upgrading, workplace breakout, thinking leap, 200 thousand + code farm growth charging station, and growing up with you.

Tags: Front-end Fragment github Android Python

Posted on Mon, 23 Mar 2020 11:33:03 -0400 by clapton