欢迎光临散文网 会员登陆 & 注册

软件开发为啥强调“单向数据流”

2021-07-18 12:17 作者:屋顶头牌  | 我要投稿

Bug的出现

        先来看1个笔者在实际开发中遇到的Bug。

        因业务需要,需要在某个页面创建很多输入框(UITextField),来接受众多不同类型的参数,如下图:

拥有很多输入框的界面

        等用户填写完整所有数据后,点击“完成保存”,程序会在此时将输入框中的数据逐个写到一个data model上,随后再做数据持久化,如下图:

统一收集UI数据回填到data模型

        需要提到的是 dataModel 以及 众多输入框控件都被同一个视图控制器持有。为什么不在输入框完成编辑的时候通过delegate立即将数据变化同步到dataModel?因为页面上的输入框实在太多了,一个个的都通过delegate来传递数据给视图控制器,意味着在delegate里要出现大量的if-else分支来判断数据变化是来自哪个输入框,这将是异常笨拙凌乱的代码,所以作罢。

        索性编辑的时候就让用户爱怎么弄就怎么弄,我们等最后完成保存的时候再统一将数据从UI控件中更新到dataModel,之后爱干嘛干嘛好了。怎么样?这种做法看起来是不是非常nice,省时省力不折腾😃?

        万万没想到,bug出现了。

        它的症状是这样,无论用户修改多少个UI输入框中的信息,点击保存后,最多只有1个变动能够被成功保存,有时甚至连1个变动也没法保存!

原因分析

        在花费了几个小时的盘查分析后,终于发现了问题的原因所在,如下图:

错误的调用地点

        图中的refreshUI()函数,作用是将dataModel的各项属性数据应用到UI上,包括那些众多的输入框。而这个refreshUI()函数本身是没有任何问题的,之所以出问题,是因为千不该万不该地在dataModel的 didSet() 方法里去调用它。

        这个didSet方法是Swift语言提供的一项便利语法糖,作用是某个属性被修改时会触发调用,然后开发者可以在里面做些想要的事情,如保存数据之类的,类似OC中手写的Setter方法。

        笔者在构建该页面的时候心想,既然页面要呈现传入的dataModel,那就索性在dataModel发生变化的时候去调用refreshUI刷新各种UI控件,这样多好,关键是有现成的didSet方法可以用!

        于是就出现了上面提到的问题,dataModel本质上是Struct,里面的属性propA, propB, propC ....对应着UI上的输入框A、B、C....。设想用户修改了众多输入框信息后,点击保存,触发统一的写数据逻辑writeInfoToData(),在该方法运行到第一行代码时


     data.propA = textFieldA.text

        

该dataModel的didSet方法被触发,进而refreshUI()被触发,其中的代码被悄无声息地执行     

   

     textFieldA.text = data.propA

     textFieldB.text = data.propB

     ...

   

        老天!data.propB可是修改前的旧数据~  

        这样执行下来的结果,就是输入框B,C,D....中的内容都被强制更新成了用户修改之前的旧值,等于用户的修改除了输入框A,其他的全部被强制撤销了!

怎么补救?

        可以看到,造成问题的原因在于,笔者尝试在dataModel数据变动的回调方法(didSet)里去刷新UI控件,之后企图在用户点击保存的时候从UI控件中将数据写回到dataModel,而这个写回操作又会触发dataModel的数据变动回调方法,形成了相互调用。进一步讲,数据一会儿从dataModel流向UI控件,一会儿又从UI控件流向dataModel,最终酿成了Bug。  

        加上执行完writeInfoToData()后,页面就立即销毁了,看不到输入框中的修改值被强制重置为旧值,这又增加了发现Bug的难度。

        最后的解决办法是将refreshUI从dataModel 的didSet方法里摘除,放到ViewDidLoad中执行,随后问题消失了。

感想

        之前做RN项目的时候,官方文档一直在强调一个术语“单向数据流”(貌似搞Web前端的朋友对这个概念更熟悉,奇怪App端开发知道这个的并不多)。但当时也只是按照规定做事,具体为什么要保证“单向数据流”却没有弄清楚。这次算是真的碰到了实际Bug,才知道单向数据流之于程序稳定性的重要,因为不这么弄容易出Bug。    

        再有就是,新的高级语言会提供很多语法糖之类的东西,使得相对于之前的语言,会有很多非常省事方便的操作来使用键值观察、信号传递之类的特性。这就导致很多开发者滥用这些语法糖、操作,因为它们可以让代码写的更少,相对于开发者老老实实地用代理,自定义函数方法来实现。   

        后者虽然繁琐,但调用逻辑清晰,利于代码维护和Bug定位。而这又得扯到开发效率和代码健壮性之间的平衡,是另外一个话题了。


P.s:  用作Bug分析的源码可以从下面的地址获取:

https://gitee.com/BeiTianSoftware/bugdemo.git


软件开发为啥强调“单向数据流”的评论 (共 条)

分享到微博请遵守国家法律