基于接口编程

2019-08-09

  桌面端软件开发者可能不大注意到这个概念。可能做Java Web的同学对于这个概念接触的更多,理解会比较深入。Java语言比C++更加注重接口概念,提出单独的interface关键字。而且,Java支持的RTTI也倚重接口概念。两种语言上细微的差异其实并不是关键,而在于开发者是否会多花费一些时间去遵守这个标准,多写一些代码,从而提高系统的模块化。我们经常接口两方面特性:限制对象行为边界,就是对象或者模块提供了那些函数;接口两侧提供数据信息。亦即接口参数问题。本文从这两方面加以讨论。
  限制对象的行为边界是我们最为关注的地方,它决定了众多开发者是否有可能错误的使用他人负责模块的能力,从而导致系统级的逻辑错误。我之前的文章就提到过,把一个模块进行足够粒度的抽象,提出接口,让真实的模块实现该接口,其目的并不是为了有一天能够替换掉该模块。我真没有见到有人这么做。现在的迭代式开发流程中,基本上没有设计好的地方,就会找时间直接重写了。系统的变动鲜有局部变动,等到要修改系统的时候,基本上好多个模块都需要重构了。更多的情况,一个模块接口,我们可能会提供两种实现。最常见的例子就是单元测试时打桩的需求。其他的模块都在同步开发,甚至都无法build,更不用说被使用了。或者即使编写好了,也没有办法在日常开发环境中使用时,可以花费很短的时间,就能写出符合模块接口的mock 模块,把该对象给入系统,就能让系统走完业务流程。接口的核心就是抽象。有同学不理 解模块写好了却不能用的情形,很简单,举个例子,我们的业务里有利用各种通信方式,控制机器人,获取传感器参数的模块。怎么可能给每个人分配一个价值几十万的机器人、几十万的各种传感器?还给每个人分配一个大房间,就在机器人旁边开发、调试?
  首先,需要对常见的概念做抽象,看看是否需要提出接口。也许需要,也许不需要。可有可无之时,就可以不做。即使将来需要,影响的也只有模块内部,很小一部分代码而已。对于越重要的东西做抽象,其意义就越大。这个过程能帮助我们认识清楚模块中各种概念的核心,我们分析的越清楚,就越不容易犯错误,就能构建越稳定的设计。
  其次,对模块做抽象,提出接口。对于一些辅助性的模块,如log、监控、工具等,它们本来就不重要,也基本不影响业务流程,我们也无需管。对于一般重要的模块,如文件读写、脚本引擎等,一般选择好了实现方式后,就基本上不会再修改了。技术负责人应该保证这一点的。这类型模块,我相信没有人愿意抽象出接口,准备有一天替换掉已有的模块实现吧。对于核心的业务模块,如控制硬件、网络通信、处理业务数据等核心的模块,就需要提炼出模块接口。
  编程语言接口提供接口的机制,当然能够帮助我们更好的解决问题。如Python、Go采用duck type,python只能在运行时发现可能不符合接口,Go只能在接口实现类型被使用时,才能被编译器发现不符合接口。C++/Java这样的强类型语言,则要求接口实现类型自己在编译时,就需要严格符合接口。越早检查,就能越早发现问题。越晚检查,逻辑就能够越灵活。我们需要有所选择。
  接口参数如何设计,也重要。设计的好,我们就不需要减少修改接口的次数,亦减少了关联的改动。要知道,软件开发里,只要代码能工作,就不要去动它。如果做好了抽象,接口函数直接接受各种对象指针,就是最简单的方式了。如果接口需要很动态的信息,不适合抽象出很多的POD或者业务类,一种直观的做法就是使用动态的数据结构:JSON,或者简单的数组与map,
  关于接口,我们可以从更加广义的角度来看这个特征。程序与所需文件格式的要求是否可以当作接口?多组件通信的格式是否可以看作是接口? 例如,跨语言系统,如PHP front end,Java/C++ Backend构成的Web系统,如何通信。相当于PHP做view/controler,Java做model的工作。一般而言,有两种常用方案:protobuf/thrif方式、json/xml方式。我毕业后,就在公司的项目尝试过json方案,结果不理想,很不理想,json encode/decode比预想的要麻烦的多。而且,class member 退化为 json key 字符串,所带来的接口不明确问题,也很严重。一个service接口,可能在被五六个controller使用,分别由两三个人负责,那么controller个所需的数据,都杂糅在接口参数json里。在两三年的时间内,service接口可能被修改十余次,我相信,程序员是不愿意跟进这个接口json里到底是什么结构的,我们队员就是这么干的。甚至在json object不同的层次出现的重复的参数。把众多信息按照一定规范装进这些容器里,如果没有文档,容易使用错误的。为了便捷性而牺牲一定安全性,需要结合业务特点来判断是否值得。
  另外一个例子,如带有数据库的程序,C++做view/controller,C++与DB做model,相信做惯了Java/PHP的开发者不会意识到C++里访问数据库字段的麻烦程度。如果要做封装,同样有两种方式:最简单的table row 封装为json object,或者转换为C++ POD class。我认为,针对不同的业务类型,需要不同的选择,而非都可以。对于工业应用程序,我不推荐使用JSON方式。因为工业程序的使用上,追求极致稳定,迭代版本有限。使用json的一个问题就是本该是classs member的表达式,被json key替代,甚至某个少用的逻辑分支里,很晚才能报出一个key not found 的错误。增加一个DAO层,转换为POD为佳。
  我之前参与项目的分布式渲染系统中,管理服务器与渲染节点之间 没有采用json通信方案,而是采用了共用POD class 的方案。虽然系统很简单,但是没有采用json让整个系统更加简单。增加的限制就是同步更新POD 代码文件,直接采用相同的build环境,程序都跑在windows平台上,毫无障碍,好处更多。

2019/9/21
前面写了强类型、弱类型接口的一些区别。我觉得,还需要提一点,弱类型接口中,你根本不知道接口参数里面有什么。或者在C++里面,你难以debug看到里面的内容,难以通过静态代码分析来查看接口参数内容。真真的要命。犹如Common Lisp中动态作用域给人糟糕的感觉,心慌慌。对于新接手的人,在看代码熟悉项目的过程中,难度增加了不少。

如果有任何意见,欢迎留言讨论。


[ 主页 ]
COMMENTS
POST A COMMENT

(optional)



(optional)