代码是如何越写越烂的?

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

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

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

protected void initView() {PagerAdapter pagerAdapter = new PagerAdapter();viewPagerFix.setOffscreenPageLimit(4);viewPagerFix.setAdapter(pagerAdapter);mFragmentBinding.tabLayout.setTabData(pagerAdapter.titles);mFragmentBinding.tabLayout.setOnTabSelectListener(new OnTabSelectListener() {@Overridepublic void onTabSelect(int position) {viewPagerFix.setCurrentItem(position);}@Overridepublic void onTabReselect(int position) {}});viewPagerFix.addOnPageChangeListener(new ViewPagerFix.OnPageChangeListener() {@Overridepublic void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {KeyboardUtils.hideSoftInput(getActivity());}@Overridepublic void onPageSelected(int position) {mFragmentBinding.tabLayout.setCurrentTab(position);if (mViewModel.getXXXDetailTouchManager().isZZBG()) { zzbgPageSelected(position);} else if (mViewModel.getXXXDetailTouchManager().isYBJZ()) { ...} else {...}}@Overridepublic void onPageScrollStateChanged(int state) {}});viewPagerFix.setCurrentItem(0);mFragmentBinding.headContainer.getTitleView().setOnClickListener(new View.OnClickListener() {@Overridepublic 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);}}@Overridepublic 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 {....}}@Overridepublic 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;}}@Overridepublic 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;}}

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

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

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

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

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

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

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

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

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

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

/* 友情提示:本类涂有防腐药品,切勿触碰,切勿触碰,切勿触碰!* <p>* 地区定制功能,包括特色的布局等,请继承于 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() {@Overridepublic void onTabSelect(int position) {viewPagerFix.setCurrentItem(position);}@Overridepublic void onTabReselect(int position) {}});viewPagerFix.addOnPageChangeListener(new ViewPagerFix.OnPageChangeListener() {@Overridepublic void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {KeyboardUtils.hideSoftInput(getActivity());}@Overridepublic void onPageSelected(int position) {mFragmentBinding.tabLayout.setCurrentTab(position);mDetailChildFragmentManager.onPageSelected(position);}@Overridepublic void onPageScrollStateChanged(int state) {}});}/*** viewPager适配器*/private class PagerAdapter extends FragmentPagerAdapter {String[] titles;PagerAdapter() {super(getChildFragmentManager());titles = mDetailChildFragmentManager.getTitles();}@Overridepublic Fragment getItem(int position) {return mDetailChildFragmentManager.getItem(position);}@Overridepublic Object instantiateItem(ViewGroup container, int position) {Object object = super.instantiateItem(container, position);return mDetailChildFragmentManager.instantiateItem(container, position, object);}@Overridepublic int getCount() {return mDetailChildFragmentManager.getCount();}}
}

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

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

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

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

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>() {@Overridepublic void onSuccess(MultiAddOrRemoveUseCase.ResponseValue response) {ToastUtils.showShort(getApplicationContext(), "操作成功");clearData();loadData(true, true);if (null != mNavigator) {mNavigator.dismissProgressDialog();}}@Overridepublic void onError() {ToastUtils.showShort(getApplicationContext(), "操作失败");if (null != mNavigator) {mNavigator.dismissProgressDialog();}}});}
}

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

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

到底怎样才算解耦

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

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

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

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

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

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

public class XXXListBusinessProxy extends BaseBusiness<XXXBus> implements IXXXListFragmentRequest {@Overridepublic 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;});}@Overridepublic void refreshPatternOfXXXList(final XXXListDTO dto) {handleRequest((e) -> {...count.setMyXXXCount(wyhcJgDBManager.getMyXXXPatternCount());return new Result(XXXDataResultCode.XXX_LIST_FRAGMENT_REFRESH_COUNT, count);});}@Overridepublic 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);...@Overridepublic 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:}}
}

解耦有什么好处?

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

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

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

软件工程同理。

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

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

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

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

总结

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

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

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

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

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

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

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

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

  1. 我是如何在12周内由零基础成为一名程序员的——谨以此文激励自己!!!

    我的故事 在海军陆战队服役超过10年后,我于去年7月份退役了.随后在8月份找到了一份赌场的工作做公关,到今年2月中旬的时候又被辞退了.到5月中旬的时候我在DE协会找到了一份临时的"初级用户体 ...

  2. 我是如何在 10 分钟内搞砸 IT 面试的

    最近,我差点儿就拿下了 FAMGA(CSDN 编者注:国内有 BAT,国外有 FAMGA,即 Facebook.Apple.Microsoft.Google.Amazon.)的工作机会.通过了电话面试 ...

  3. 25000linux集群危机怎么样,我是如何在2小时内组建5000+集群服务器僵尸网络的

    由于Elasticsearch命令执行漏洞,导致上万服务器受影响,截图所有ip无重复.2小时之内顺利在5000多台服务器上执行相关命令. 本次仅是技术测试漏洞影响范围,标题党了.国内测试700台集群服 ...

  4. 佳能相机照片误删怎么恢复?看看我是如何在10分钟内解决的

    佳能相机照片误删怎么恢复?说起相机的演变还真是一段辛酸史,从最开始使用胶卷才能拍照的相机随着技术的成熟不停发展,集机械.电子及光学的佳能相机腾空出世,无需胶卷,通过光学成像的原理形成影像并记录在相机的 ...

  5. 如何在90天内学会一门语言

    <wbr><wbr> 如何在90天内学会一门语言</wbr></wbr> <wbr><br><wbr><wbr ...

  6. 引导分区 pbr 数据分析_如何在1小时内引导您的分析

    引导分区 pbr 数据分析 by Tim Abraham 蒂姆·亚伯拉罕(Tim Abraham) 如何在1小时内引导您的分析 (How to bootstrap your analytics in ...

  7. 服务器创建多个dhcp服务_如何在15分钟内创建无服务器服务

    服务器创建多个dhcp服务 by Charlee Li 通过李李 如何在15分钟内创建无服务器服务 (How to create a serverless service in 15 minutes) ...

  8. 机器人坐标系建立_如何在30分钟内建立一个简单的搜索机器人

    机器人坐标系建立 by Quinn Langille 奎因·兰吉尔(Quinn Langille) 如何在30分钟内建立一个简单的搜索机器人 (How to Build A Simple Search ...

  9. bootstrap设计登录页面_前端小白如何在10分钟内打造一个爆款Web响应式登录界面?...

    对于前端小白(例如:专注后端代码N年的攻城狮),自己编写一个漂亮的Web登录页面似乎在设计上有些捉襟见肘,不懂UI设计,颜色搭配极度的混乱(主色,辅助色,配色,色彩渐变,动画效果等等,看起来一堆乱七八 ...

  10. 如何在24小时内0成本获取到25000+精准粉丝的?

    今天看到一篇干货分享文章:<如何在24小时内0成本获取到25000+精准粉丝的?>,阿泽特意分享出来,希望对大家有帮助.好了,上干货: 前言:最近做了一个公众号,试水推了一个分享链接得资源 ...

最新文章

  1. TensorFlow支持Unicode,中文NLP终于省心了
  2. 在C 函数中保存状态:registry、reference和upvalues
  3. in java中文版百度云 thinking_小程序订阅消息推送(含源码)java实现小程序推送,springboot实现微信消息推送...
  4. node 16位 转24位_C代码实现16位和32位数据字节序转换
  5. 9年前的大一,我们这样为女生过37女生节【祝节日快乐】
  6. 小程序组件的使用(三) 调用子组件方法
  7. 2014520420145212信息安全系统实验三报告
  8. python检验文件命名_Python如何检查文件名是否为UTF8?
  9. Java常量池与方法区
  10. 腾讯的“小弟”长大了
  11. 数据库知识整理 - 并发控制(封锁、两段锁协议、意向锁)
  12. R语言windows函数自动生成可视化图像画布框、使用plot函数可视化数据点图、使用type参数指定数据点为实线
  13. 翻转课堂十大精彩案例
  14. Error instantiating servlet class com.web.SelectAllServlet 所有的servlet都不能运行,终于找到解决办法
  15. 物联网控制APP入门专题(五)---使用android studio直接编写物联网控制APP
  16. GDT(全居描述符表)和LDT(局部描述符表)
  17. 创建新Docker容器时出现“The container name “/xxx“ is already in use by container xxxxxxxxxxx...”问题的解决办法
  18. 去静态化 php,PHP页面静态化 - 菜鸟要飞啊的IT小窝 - OSCHINA - 中文开源技术交流社区...
  19. Deep3D: Fully Automatic 2D-to-3D Video Conversion with Deep Convolutional Neural Networks
  20. VUE 学习笔记(三) Vue 渲染流程详解

热门文章

  1. mysql dba 试题_MySQLDBA面试题-上海热璞科技
  2. neo4j入门(一)概述
  3. 15分钟搞定OLAP查询引擎Phoenix
  4. 使用JDBC+JSP分层实现新闻管理系统注册、登录功能
  5. 算法_EXCEL中 A表示第一列,B表示第二列...AA表示27列,AB表示28列,问随意一组字母是多少列
  6. 趣闻|论文不必参考任何文献?看到作者,网友大呼失敬了
  7. 给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
  8. 柱状图之最大矩形面积
  9. 哈希表中处理冲突的方法
  10. kaggle实战之流浪猫狗归处预测