1. 程式人生 > >有一次接口設計

有一次接口設計

更多 ould out count 兼容性 互調 java num col

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

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

有一次接口設計