有一次接口设计

小李最近手头在做的task,需要暴露新的接口出去给客户。

========================我是正文分割线=============================

<<<<<<<需求>>>>>>>

----------------------------------------------------------------------------------------------------------

需要暴露一个汽车特征点的接口,输入是一张图像,输出是汽车上的特征点,landmark。

----------------------------------------------------------------------------------------------------------

so easy?输入基本不用管,输出那就定义一个结构体不就完事了吗?假设这一代的算法支持一辆车100个ladmark点。over了。

版本1

typed struct LM

{

uint x[100];

uint y[100];

}

方法很简单,问题很明显:

1.如果算法升级了,支持的特征点数变多了,怎么破?直接把点数写死是不是不太好?

2.将来怎么扩展?如果需要支持更多的东西,比如车的颜色,型号等等呢?

好,所以小李就跑去跟同事讨论,最后得到了一个高灵活度的接口

版本2

typedef struct LM

{

struct Header

     {

          uint LMCount;

          uint x_offset; //4 bytes for each x

          uint y_offset; //4 bytes for each y

     }

     UINT   nValue;

}

定义了一个头,一个数据段,在header里面指定了landmark的个数,然后是x坐标相对于数据段起始地址的偏移量,这样,用户在调用接口的时候,拿到这个结构之后,一看,哦,有100个特征点,然后从nValue开始第0x00就是x的坐标,一个x占用4 bytes,一共去拿100个x,嗯,y也是一样的,就结束了。

小李觉得不错,你看,将来不管算法是支持多少个特征点,都不会存在兼容性的问题,蛮好蛮好,就提交给老板了。

老板一看,小李你这搞得啥玩意,根本不知道这个x和y是在内存中怎么layout的,不就是个xy坐标吗,整的这么复杂,我都看不懂用户怎么看得懂,打回去,让小李搞个简单点的。

郁闷的小李又开始苦思冥想,不让用offset的方式,又不能把数量写死,那就这样吧

版本3

typed struct LM

{

    uint reserved[4];

    struct LMdata

    {

        uint x;

        uint y;

    }data[1];

}

让用户通过一个新的接口先去拿一下landmark的数量,然后拿到数量后,分配内存,之后用户拿到这个结构体之后,就可以去拿[x,y]了。这样,数量没有写死,结构清晰,还加了一个reserved,便于后续扩展。

老板觉得小李的方法不错,然后reserved[4]的可扩展性有限,建议改成指针,于是就有了版本4

版本4

typed struct LM

{

    struct LMdata

    {

        uint x;

        uint y;

    }data[1];

   void* future;

}


小李和老板都没有看出这个结构体有啥问题,就决定找技术老王来看看,没啥问题就done了。

老王很快指出了4个问题

1.当用户在使用这个结构体的时候,定义LM plandmark,那么plandmark.future是啥?应该是第二个LMdata,而不是真正的furture,这样的定义一定是不可以接受的

2.这个结构体用户在拿去用的时候,不知道是什么样的layout,不知道lmdata究竟又多少个,这个结构体本身不独立

3.在用户和算法的dll之间传数据的时候,future是作为一个指针存在的,那么用户的这个指针是在用户的进程里面有效的,如果我们这个dll不跟用户在同一个进程里面,那这个指针传递是很不靠谱的

4.void在32位系统里面是4个byte,在64位系统里面是8个byte,如果恰好是app和dll之间的位数不一致,那么对于这个地址的解析也会不一样,肯定是有大问题的

小李傻了,原来把一个指针引入到接口的结构体里面有这么多的问题。啥也不说了,开始改吧。

1好说,直接把future指针放到结构体开头就好了;2也好说,加一个LMcount就行,3的话目前是可以保证的,4的话可以用void64解决

版本5

typed struct LM

{

pvoid64 future;// provide by user, numm for now

uint LMcount;

    struct LMdata

    {

        uint x;

        uint y;

    }data[1];

}

这个结构体出来之后,做linux的小林路过看了一眼,pvoid64这个在linux里面并没有定义,作为一个跨OS的接口,这样显然不合适

小李(几乎崩溃,思维不清):那则么办啊,用unsigned long long *可以吗?

小林:当然不行了,unsigned long long*是什么意思啊,他指的是指向的是一个long long类型的变量,但是指针本身的长度不变啊,32位里面是32位,64位里面是64位。

小李:不是把,让我想想。。。。我们为什么一定要用void指针啊?

小林:void指针又叫做无类型指针,可以通过强转转成其他类型的指针,这个pvoid和pvoid64其实是说指针的长度,像这种定义其他的类型都是做不到的。

小李:oh my gosh,为啥linux里面没有这么好用的pvoid64?那现在怎么办?

小林:只好把指针定义为unsigned long long类型,用的时候再转成指针了

版本6

typed struct LM

{

unsigned long long future_ptr;// provide by user, numm for now

uint LMcount;

    struct LMdata

    {

        uint x;

        uint y;

    }data[1];

}

小李,老板,老王, 小林一起review了这个接口,终于通过了。

=================我是干货==========================

1.定义这种结构体需要考虑存储的有效性,即structure尽量是4个bytes对齐,剩下的可以用reserved去填充

2.基本的要求是尽量直接传数据,然后用户能够清晰的知道结构的的layout,不能太复杂;用户有方式知道分配多大的buffer,然后拿到这个Buffer之后能够简单的解析,比如A.b需要就是指向b的,不能有歧义,所以可扩展大小的放在结构体最后,之后不要再加其他的了

3.可扩展性,如果有些参数随着算法升级会有变化,就需要考虑可扩展性

4.API中不能出现pviod这种含混不清的字眼,极有可能在32位和64位相互调用的时候出错

5.结构体的独立性,用户拿到这个结构体就可以开始解析,不需要借助其他的接口再去拿什么值

6.跨平台的接口,考虑linux,比如pvoid64这个在linux里面就没有,要使用常见的类型

7.使用指针要慎重!!!!要考虑是否能保证在一个进程里面

====================我是花絮========================

review完后,小李心想,这个接口虽然定义了这么久,但是其实定义的并不好,老王之前提到的不在同一个进程当中,就是一个很严重的潜在问题,所以在未来的接口定义中尽量不要使用指针。

小李上网查了查别人定义的接口,发现在java下定义接口好简单啊,不用考虑内存分配的问题,C++果然需要考虑的很多。在c++下有什么好的解决这种类似问题的方法吗?学习一下微软,发现可以采用定义version的方法,其实每次根据这个version就可以知道是算法的第几个版本,然后用不同的struct去转换,这个方法应该是比较好的。

typed struct LM

{

    struct header

    {

        uint version;

        uint size;

    }

    uint data;

}

typed struct LM_1

{

    struct header

    {

        uint version;

        uint size;//read only

    }

    UINT  type;

    UINT  position;//add whatever future feature here

    struct LMdata

    {

        uint x;

        uint y;

    }data[100];//could be 200 in v2, 300 in v3, as you wish :)

}

在code里面这样转换就好了

if version == 1

(LM_1*) plm = (LM_1*)p_landmark

else if .....

===============我是彩蛋=======================

原文地址:https://www.cnblogs.com/sunny-li/p/9383331.html

时间: 2024-10-27 16:27:04

有一次接口设计的相关文章

微信小程序的Web API接口设计及常见接口实现

微信小程序给我们提供了一个很好的开发平台,可以用于展现各种数据和实现丰富的功能,通过小程序的请求Web API 平台获取JSON数据后,可以在小程序界面上进行数据的动态展示.在数据的关键 一环中,我们设计和编写Web API平台是非常重要的,通过这个我们可以实现数据的集中控制和管理,本篇随笔介绍基于Asp.NET MVC的Web API接口层的设计和常见接口代码的展示,以便展示我们常规Web API接口层的接口代码设计.参数的处理等内容. 1.Web API整体性的架构设计 我们整体性的架构设计

2.35 Java基础总结①抽象②接口③设计抽象类和接口的原则④接口和抽象类的区别

java基础总结①抽象②接口③设计抽象类和接口的原则④接口和抽象类的区别 一.抽象 abstract作用:不能产生对象,充当父类,强制子类正确实现重写方法和类相比仅有的改变是不能产生对象,其他的都有,包括构造.属性等等任何一个类只要有一个抽象的方法就成了抽象类 抽象方法 public abstract A();①方法是抽象的,这个类也是抽象的:②子类必须重写抽象方法,除非子类也是抽象类 抽象类可以没有抽象方法,但一般不这么设计 二.接口 interface 接口也是Java的一种引用数据类型(J

php后台对接ios,安卓,API接口设计和实践完全攻略,涨薪必备技能

2016年12月29日13:45:27 关于接口设计要说的东西很多,可能写一个系列都可以,vsd图都得画很多张,但是由于个人时间和精力有限,所有有些东西后面再补充 说道接口设计第一反应就是restful api 请明白一点,这个只是设计指导思想,也就是设计风格 ,比如你需要遵循这些原则 原则条件REST 指的是一组架构约束条件和原则.满足这些约束条件和原则的应用程序或设计就是 RESTful.Web 应用程序最重要的 REST 原则是,客户端和服务器之间的交互在请求之间是无状态的.从客户端到服务

App接口设计原则-b

1.记住密码不是真的让你记住密码,这里仅仅指的是一种自动登录的手段.不管在任何地方,明文存储的密码都是安全隐患,是必须尽量避免的.你可以采用某种方式对用户名.密码以及时间戳(重要)进行签名,再次登录时使用签名后的数据进行登录. 2.客户端要包含超时机制,见过不止一次的服务端设计是通过客户端发送的userid来区分用户的.对此我真的已经无力吐槽了.最简单的办法是用session来记录用户状态.当然,考虑到客户端的特性,用户可能好几天都没有关闭一次,必须配合自动登录机制来保证客户端的在线. 3.接口

蓝牙(BLE)应用框架接口设计和应用开发——以TI CC2541为例

本文从功能需求的角度分析一般蓝牙BLE单芯片的应用框架(SDK Framework)的接口设计过程,并以TI CC2541为例说明BLE的应用开发方法. 一.应用框架(Framework) 我们熟知的Framework包括Android Framework.Linux QT.Windows MFC.应用框架抽象并封装实现了一般应用场景的需求,完成应用开发的80%,剩下的20%则以回调(callback)和接口的方式供应用开发人员调用以完成具体的需求. 一般Framework完成的工作包括:任务分

C++ 11可变参数接口设计在模板编程中应用的一点点总结

概述 本人对模板编程的应用并非很深,若要用一句话总结我个人对模板编程的理解,我想说的是:模板编程是对类定义的弱化. 如何理解“类定义的弱化”? 一个完整的类有如下几部分组成: 类的名称: 类的成员变量(或属性,C#中属性和成员变量还是有区别的): 类的成员方法: 从编译器的角度看,我们必须明确指定以上3部分,才算完整地定义了一个类并且编译通过. 所谓的“类弱化”,是指类的设计者在定义类的时候,并没有完整定义一个类,而是把类的其中一部分的定义留给类的使用者. 从传统才c++98看,通过模板类,使用

Atitit.自定义存储引擎的接口设计&#160;api&#160;标准化&#160;attilax&#160;总结&#160;&#160;mysql

Atitit.自定义存储引擎的接口设计 api 标准化 attilax 总结  mysql 1. 图16.1:MySQL体系结构1 2. 16.7. 创建表create()虚拟函数:2 3. 16.8. 打开表 open()2 4. ---------------------------------------------------------------------------------------------------------------------2 5. 16.9. 实施基本的

PHP与Spring的强势接口设计_微课介绍

[back]  微课名称: PHP与Spring之间的强势接口设计  微课介绍:    PS.本頁建置中,請先閱讀博文:<<PHP与Spring之间的强势接口设计>> ~ End ~

【Android】透明状态栏在App中的实现与接口设计

By Sodino 文章目录 1. 认识透明状态栏 2. 透明状态栏Api及特性 3. 设置透明状态栏 4. 处理消失的系统状态栏区域 5. fitsSystemWindows 6. Activity中的接口设计 7. Fragment中的接口设计 8. 白色Titlebar的处理 9. 小米 与 魅族 与 (莫名其妙的)华为 10. 腾讯优测UTest GitHub源码:TransparentStatusbar源码中分两个app TestBasic: 透明状态栏实现的示例,方便debug 白色

C++二进制输入输出流接口设计

提到输入输出流,作为CPPer很自然的就会想到std::iostream,对于文本流的处理,iostream可以说足够强大,应付一般复杂度的需求毫无压力.对二进制流处理却只能用“简陋”来形容,悲催的是,作为一个在多媒体软件领域默默耕耘多年的码农日常打交道最多的偏偏就是二进制流. 前些年流行过一本书叫做什么男人来自火星女人来自金星之类的,同样的,如果说文本流来自火星那二进制流就是来自进行.对一个文本流,我们可能期望这样的接口函数: 1 string text = stream.get_line()