• 有一次接口设计


    小李最近手头在做的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 .....

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

  • 相关阅读:
    postgis 利用 php 返回geojson格式数据
    openlayers 3读取加载geojson格式数据
    openlayers 3加载百度、高德、google瓦片地图
    ol2 和 bootstrap样式冲突的问题
    Openlayers 2 取消鼠标缩放地图的功能
    Struts2之2.5.10配置
    ol3修改右下键的Attribution
    openlayers 2 高亮显示元素以及通过属性查询高亮某一元素
    sql查看锁与解锁
    使用jQuery解析JSON数据
  • 原文地址:https://www.cnblogs.com/sunny-li/p/9383331.html
Copyright © 2020-2023  润新知