[Android]使用MVP解决技术债务(翻译)


以下内容为原创,欢迎转载,转载请注明
来自天天博客:http://www.cnblogs.com/tiantianbyconan/p/5892671.html

使用MVP解决技术债务

原文:https://medium.com/picnic-engineering/tackling-technical-debt-with-mvp-67e805ed5103#.couu0d5i0

免责申明:这篇博客并不是讲关于怎么使用MVP的方式(上帝知道关于这些已经太多了)去写Android代码。而仅仅是我的个人经验,关于怎么转换我们的表现层到MVP架构来帮助我们解决一些累积的技术债务,而且在这个过程中也会帮助我们的app从一个原型转变成一个更具维护性的产品。

任何从事Android工作足够久、项目足够大的开发者最有可能达到一个点,他们面对他们的代码库,觉得应该有更好的实现方案。我们在Picnic也是一样,在Android app开发开始后大约八个月,我们到达了的那一刻,就在我们向公众发布第一个版本的时候。

这一刻正好是在我们app推出的时候这也并不意外。直到那时,我们以一个非常快的速度在前进,不断敲打我们的键盘,从零开始构建一个完整的产品,尝试新的东西,结合用户反馈到我们的app中,在每天的基础上增加和丢弃特性。

为了跟上公司的速度我们砍掉了这里那里的边边角角。这样的工作对我们来说很好,这也是我们能够在这么短的时间内构建这个app的原因之一。但是正如预期那样,最后这些决定的影响开始以技术债务的形式显示出来。幸运的是这些技术债务是在数月之内建立的,在app的性能和稳定性上面并没有任何真正的影响。反而我们是在其它领域开始注意到它:

  • 新功能迭代时间的增加。
  • 新入职的开发者遇到困难
  • 它被证实难以实现自动化测试
  • 整体功能的复杂性在增加

我们已经有了一个很好的想法和一个易于理解的架构,用于网络层、错误处理和app内部模块通信。但是像大多数Android开发者,我们会对把太多的逻辑放进Activity和Fragment中会产生内疚。

旁注:这是Android开发者的共同的问题,而作为开发者需要在黑暗中摸索,因为Google对这个话题保持沉默。我们从它们那里得到的第一个(算是)官方回复是来自Android团队的一个开发者在 Google+ post,说明我们应该把核心的Android API作为一个‘系统框架’,意味着他们会带我们手把手地到达Android核心的组件(Activity, BroadcastReceiver, Service 和 ContentProvider)。之后我们做什么都是看我们自己了。而且就在最近,Google终于提供了一系列的例子用来解决关于怎么构建一个Android app的共同问题,它着重于MVP。尽管只是beta,但是它可以在这里查看:Android Architecture Blueprints。

无论如何,这其实是一件好事,因为这意味着我们可以自由地去实验任何我们喜欢的方式,而不是被强制在一个平台遵循一个特定的模式。

现在讲回我们的故事… 除非你处在Android开发世界的远古时期,你应该会注意到表现层架构是现在的热门。关于最好的方式是什么,每个人甚至连他妈妈似乎都有自己的观点。工作中标准的Android方式(类似MVC),到MVP,到通过data-binding的MVVM,所有的方式都沿用了 Uncle Bob 的 clean architecture。每一种方式围绕赞成或者反对的意见都有一些有趣的讨论,但是有一件事我们要明确知道,那就是我们应该避免喝Kool-Aid(译者注:这里是比喻,表示非常愚昧地接受信奉某种观点或者思想)和期望其中一种是银色子弹(译者注:这里是比喻为具有极端有效性的解决方法)然后永远解决所有问题。

当在考虑怎么去重构我们的表现层时,我们已经有近一年的代码库的积累,我们很清楚我们的缺陷在哪里,然后我们需要使用一个新的实现(以上主要表示一些能够解决我们的技术债务的点)来达到我们的目标。我们在虚拟的项目中试玩了一些,体验了各种方法的不同之处,然后最终决定使用MVP。从它的核心来说,MVP本身仅仅是一个概念,而Android框架,根据设计,并不强制任何模式,我们可以自由地选择实际的实现细节。

在Android团队中,首先我们是不过度工程的信徒,让代码随着时间的推移自然地发展,而不是过早地在试图为自己不可预知的未来做准备的抽象之上增加抽象。正因为这个原因,我们选择另一风味的MVP,使得可以最低限度地保持我们的抽象层次。在代码级别,这意味着有一个单独的接口来表示View。所有其它的组件都是具体的类。你可能会问自己,怎么会只有View使用接口?考虑到我们迫切的需要,这是真正受益于这样的接口的唯一的组件,因为我们实际上有不同的具体的Views来共享相同的接口。所以在我们的案例中,这里的一个接口将被允许我们去重用Presenters。一些MVP实现建议给所有组件(M,V和P)设置接口。尽管这样会工作得很完美,但是我们在较早的阶段并不提倡,因为添加之后的成本是代码可读性和维护性,尤其是当我们考虑到新入职对MVP陌生的初级开发者的时候,好处超过面向接口编程的方式。

相比其他,MVP实现是非常标准的。View(Activity,Fragment或者一个自定义View)负责创造和维护Presenter,而Presenter处理各种业务相关的逻辑(数据获取,存储,格式化等等),然后根据需要通过更新UI回调到View。在我们的案例中,数据层已经是相当模块化了,构造用于表示数据模型的POJOs,以及一个预先存在的控制层用于处理网络通信。

这是一个非常标准的MVP设置,也因为它很简单,我们可以在几周的时间内替换几乎我们的所有的UI代码。因为我们已经存在独立的数据层来处理所有与后端的API交互,所以真正需要重构的只是Views和Presenters的交互。

在重构的过程中,我们也学习了一些可能会派得上用场的东西:

  • 生命周期:因为Presenter是View创建的,我们需要确保完全地理解View的生命周期,特别是因为它将最有可能去处理状态更新和异步数据。举个例子,每一个Presenter应该在View destroyed的情况下有一个取消异步任务的方式,或者应该在用户暂停或者恢复视图事件时重置到原始状态等等。最后但同样重要的是,当View已经被销毁,试图从Presenter去更新View元素,始终需要注意可怕的NPEs。

  • 保持Views尽可能地愚蠢:我们的Views应该不再包含任何业务相关的逻辑。它应该只包含Android框架inflate和设置View的这些最低限度的东西。任何用户交互应该派发到Presenter。根据经验,如果你的views有任何其它方法去更新UI元素或者响应用户触发的事件,那么你可能应该去检查它们的实现。

  • 保持Presenter尽可能地纯粹:这一点,我们的意思时你应该尽可能地避免有Android相关的代码在你的presenters中。为这些组件编写纯粹的单元测试,而不需要使用其它如Robolectric等测试框架,这明显地得到了简化。这明显说起来比做起来容易得多,因为你终归会在某些地方遇到这种情况,举个例子,你将需要有一个Context的引用用来比如数据加载、访问strings文件等等。

结论

那么,说了那么多,最终的结论是什么呢?总的来说,我很高兴使用了MVP。它一定程度上帮我们解决了我们快速开发所累积的技术债务,然后,我们准备了更多来针对第二阶段的开发。

一些值得一提的事情:

  • 测试数:在重构之前,测试的数量用两只手都可以数得过来。这是一个巨大的任务来针对包含了所有逻辑如执行数据解析、格式化、网络请求、错误处理和管理自己的生命周期的Activity编写测试。仅思考如果在这些条件下编写测试就足以让我们去寻找其它的方式了。一旦转换我们的第一份代码到MVP,对此编写测试就变得碎片化了。通过一个清晰的合同明确什么View能够处理,我们可以把自己的代码与Android UI框架隔离开,然后仅仅测试实际调用的是否是正确的方法,并给出每个测试场景。现在实际的业务相关逻辑被放置在Presenters中,因为它们绝大多数都不需要有Android OS相关的认知(或者小部分相关的可以被mocked),我们也可以针对它们编写非常有效率的单元测试,因此,在过去几个月里,我们的测试用例从原来的10增加到900,而且还在增长中。

  • 可预见性:这个是有一点软度量,但是非常强大的一点。针对UI,我们选择并坚持一个通用的模式,我可以在代码库中获得可预见的好处。这意味着,无论是哪种开发者眼里的UI元素(Activity,Dialog,Fragment等等),如果理解其中一个怎么工作,那也就能理解所有怎么工作。打开一个就算不是你写的文件也不再会遇到让你觉得惊喜的东西了。明确规定职责,每一单个的UI组件都遵循相同的明确的模式。让新入职的新开发者从第一天起就是高效的,这是非常宝贵的。

我们别忘记MVP并不只是用于表现层,但是作为前端开发人员,这里花费了我们太多的时间。所以努力去寻找一个解决方案来给我们带来更好的可预见性和在新的开发者加入我们的时候也能让我们快速迭代是值得的。经过全面的考虑,我们可以有把握地说MVP是可以帮助我们达到这个目标的一个重要的里程碑。

P.S. 如果你仍然渴望看到一些源代码,这里有一个我们MVP实现‘忘记密码’用例的剥离下来的版本,展示MVP组件与用户的交互,用户点击‘重置密码’按钮进入他们的邮件地址(为保持代码的简洁,Android模版代码已经移除):

// BasePresenter.java (Base class for all our Presenters)
public abstract class BasePresenter<V> {private WeakReference<V> mView;public void bindView(@NonNull V view) {mView = new WeakReference<>(view);}public void unbindView() {mView = null;}public V getView() {if (mView == null) {return null;} else {return mView.get();}}protected final boolean isViewAttached() {return mView != null && mView.get() != null;}
}// IForgotPasswordView.java (view interface)
public interface IForgotPasswordView {void showLoading();void hideLoading();void setEmailText(String email);void showEmailNotValidError();void showPasswordRequestOk(String message);void showPasswordRequestFail();
}// ForgotPasswordFragment.java (view implementation)
public class ForgotPasswordFragment implements IForgotPasswordView,View.OnClickListener {// Triggered by the user clicking a buttonpublic void onResetPasswordClick() {String email = mEmailEditText.getText().toString();// Forward all logic to the PresentermPresenter.requestPasswordChange(email);}}// ForgotPasswordPresenter.java
public class ForgotPasswordPresenter extends BasePresenter<IForgotPasswordView> {public void requestPasswordChange(String email) {if (!Utils.isEmailValid(email)) {// Make sure the view is still alive before trying to access itif(isViewAttached()) {getView().showEmailNotValidError();    }} else {requestPasswordChangeAsync(email);}}private void requestPasswordChangeAsync(String email) {// Update the view's UI elementsif(isViewAttached()) {getView().hideKeyboard();getView().showLoading();// Call our API (results are posted back on an EventBus)api.forgotPassword(email);}}// Subscription to the event bus@Subscribepublic void onEvent(final Event event) {if (isViewAttached()) {// Update the view's UI elementsgetView().hideLoading();switch (event.getType()) {case FORGOT_PASSWORD_OK:getView().showPasswordRequestOk((String) event.getData());break;case FORGOT_PASSWORD_FAILED:getView().showPasswordRequestFail();break;}}}
}

转载于:https://www.cnblogs.com/tiantianbyconan/p/5892671.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/489894.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Science:睡眠剥夺影响大脑思考竟是因为蛋白质罢工了!

来源&#xff1a;生物探索睡眠会影响我们的思维&#xff0c;当我们获得充足的睡眠后&#xff0c;大脑思维会变得清晰&#xff1b;而当我们睡眠不足时&#xff0c;大脑会变得迟钝。那么进入睡眠状态后&#xff0c;大脑又是如何调整以保证睡醒后脑回路清晰的呢&#xff1f;近日&a…

【Matlab】滤波器常用命令

在命令行中输入&#xff1a; designfilt然后就会弹出滤波器的种类&#xff0c;选定后会让你填参数。 参数填好之后点确定&#xff0c;会在命令行窗口生成这个滤波器&#xff0c;复制到编辑器里直接用就好。 %读取音频文件% [x,Fs]audioread(C:\Users\16000\Desktop\testfile.…

element ui input视图没刷新_[Selenium自动化测试实战] 如何在UI自动化测试中加入REST API的操作...

问题当我们描述一个“好的自动化测试用例”时&#xff0c;经常出现标准是&#xff1a;精确。自动化测试用例应该测试一件事&#xff0c;只有一件事。与测试用例无关的应用程序的某个部分中的错误不应导致测试用例失败。独立。自动化测试用例不应该受测试套件中任何其他测试用例…

语句中如何结束本循环进入下一循环_Python3基础语法(八)--控制循环 while...

一、while 简介Python 的循环有 for 和 while 两种&#xff0c;while 为条件控制循环&#xff0c;通过循环控制条件表达式控制循环结束。流程图如下&#xff1a;Python 中 while 语句的格式如下&#xff1a;while <条件表达式>:【语句块】释&#xff1a;当 while 的 <…

关于生命、宇宙和万事万物的42个终极问题

来源&#xff1a;世界科技创新论坛" 我们的宇宙是否稳定&#xff0c;黑洞熵的起源和温度是什么&#xff0c;爱因斯坦的相对论和标准场论总是有效的吗&#xff0c;时空几何中是否存在奇异的性质&#xff0c;化学、应用物理和科技的极限是什么……“在达到完全开悟的道路上&…

【matlab】画图的文字调整大小

hxlabel(x); set(h,Fontsize,14);hylabel(df(x)/dx); set(h,Fontsize,14);htitle(精确解和二阶差分对比); set(h,Fontsize,14);hlegend(精确的一阶偏导,二阶差分得到的偏导); set(h,Fontsize,14);这样就行了

python decimal_【进阶】嫌弃Python慢,试试这几个方法?

(给机器学习算法与Python学习加星标&#xff0c;提升AI技能)选自towardsdatascience&#xff0c;作者&#xff1a;Martin Heinz本文转自机器之心(nearhuman2014)本文将介绍如何提升 Python 程序的效率&#xff0c;让它们运行飞快&#xff01;计时与性能分析在开始优化之前&…

王道8套有变化吗_求求你别再套花艺设计公式了

花艺设计也有公式吗&#xff1f;确实有花艺设计只有公式吗&#xff1f;并不是无论是哪门设计学科&#xff0c;公式这种东西&#xff0c;谈多了是否有种千篇一律的感觉&#xff1f;设计风格相似的花艺师要越来越多&#xff0c;一时间竟然以为都是一个人。就和网红一样&#xff0…

对象构造函数的原型图

对象的定义其实很广泛,万物皆为对象,我们创建对象一般都是用构造函数来创建的,这里我们来说说构造函数创建对象的原型图把. 这个问题有点抽象,举个例子来说,方便一点: 这是我们构造函数,这里我们要结合一张 图来说明就更清楚了,这里我们就用一个实例p1好了,其他两个就不用了. 这…

【服务器】sbatch 提交作业脚本

在已经写好的脚本的#!bin/bash下面加上&#xff1a; #SBATCH --get-user-env #SBATCH --mail-typeend #SBATCH -J simple_module #SBATCH --nodes4 #SBATCH --ntasks-per-node24然后退出即可。 运行时使用 sbatch ./name_of_bash即可 其中 #SBATCH -J simple_module指定了提…

站在AI与神经科学交叉点上的强化学习

来源&#xff1a; 混沌巡洋舰一&#xff0c;强化学习概述让机器来决策&#xff0c;首先体现在如何模仿人类的决策。对于决策这个问题&#xff0c; 对于人类是困难的&#xff0c; 对于机器就更难。而强化学习&#xff0c; 就是一套如何学习决策的方法论。强化学习最初的体现就是…

澜起科技云计算服务器_服务器严重缺货!云应用大爆发!云计算正强势起爆(附龙头)...

催化因素&#xff1a;这两天全国上千万企业、近两亿人开启在家办公模式。阿里、华为、腾讯等各大网络办公平台纷纷告急。对云服务的需求大增也让服务器生产企业开足马力&#xff0c;春节假期里&#xff0c;山东浪潮集团就接到了1500台服务器的订单。目前&#xff0c;多家软件服…

【matlab】零相位延迟滤波器

使用designfilt命令自动生成滤波器&#xff0c; 然后my_filt‘生成的滤波器代码’&#xff0c; 最后filtfilt(my_filt,signal)

博客迁移到简书

由于博客园对markdown的支持不是很好&#xff0c;hexo访问速度比较慢&#xff0c;后续博客迁移到简书&#xff0c;简书地址 http://www.jianshu.com/users/7966a93b66ce/latest_articles 。 转载于:https://www.cnblogs.com/actionke/p/5899368.html

车险赔付率分析报告_车险有变!价格…

各位车友请注意&#xff01;《商业车险综合示范条款(2020版征求意见稿)》于近日发布向社会公开征求意见从修订版条款的内容来看大幅删减了责任免除项目扩展了保险责任在最大化让利于消费者的同时努力提升消费者体验那么&#xff0c;此次修订版有哪些具体的亮点呢&#xff1f;一…

“众声喧哗”中的VR,谁来买单?

来源&#xff1a;VR每日必看未来智能实验室是人工智能学家与科学院相关机构联合成立的人工智能&#xff0c;互联网和脑科学交叉研究机构。未来智能实验室的主要工作包括&#xff1a;建立AI智能系统智商评测体系&#xff0c;开展世界人工智能智商评测&#xff1b;开展互联网&…

.npy文件_Numpy库使用入门(六)文件的存取

ERNIE&#xff1a;BERT&#xff0c;你看到我的npy了吗&#xff0c;我记得我放在这个文件夹里的呀(」&#xff1e;&#xff1c;)」BERT&#xff1a;就你还有npy&#xff1f;我还单着呢&#xffe3;へ&#xffe3;ERNIE&#xff1a;你想什么呢&#xff1f;我指的是numpy储存数据的…

【linux】设置镜像源

首先找到默认的源 $cd /etc/apt $cp source.list source.list_default然后替换掉source.list文件为国内源。 清华源

Java易混小知识——equals方法和==的区别

一、equals方法和的区别 1.equals是String对象的方法&#xff0c;可以通过".“调用。 2. 是一个运算符。 二、常用的比较用法 1、基本数据类型比较。 equals和都比较两个数值 是否相等。相等即为true,不相等则为false。 2、引用对象的比较。 equals和都比较栈内存中的地址…

redis rua解决库存问题_如何解决高并发下的库存安全问题,没你想得那么复杂(附源码)...

一、 问题不知道大家该开发中有没有遇到这样的一个问题&#xff0c;在电影院购票或者去网上买东西的时候&#xff0c;比方说当年哪吒大电影出来的时候&#xff0c;那抢票相当火爆啊&#xff0c;一票难求&#xff0c;那购票系统的后台是如何保证观众能买到自己喜欢的票同时不用担…