移動端SDK介面設計心得體會

追夢人君發表於2019-07-13

介面設計注意點

對於SDK而言,介面是連線SDK與客戶產品的紐帶,介面設計的優劣是衡量SDK產品易用性的重要指標。糟糕的SDK介面不僅僅給開發者帶來的難用的主觀印象,更有可能增加客戶的開發成本,甚至影響產品質量。
筆者在從事SDK開發的幾年起初並不重視,在一次次填坑的過程中也逐漸意識到優秀的SDK介面設計必須要思考以下幾點:

  1. 風格統一
    統一的介面設計風格不僅僅是為了給開發者留下專業的印象。更進一步的,它可以傳遞給開發者SDK的設計理念。開發者通過這種風格上的暗示,可以更直觀,不易犯錯的呼叫SDK功能。
    舉例來說,我們可以設計SDK所有介面都通過init方法初始化,uninit方法反初始化。開發者一旦接受了這種設定,會潛意識的注意有沒有呼叫過uninit釋放資源。

  2. 平臺慣例
    一款SDK產品底層通常是跨平臺實現,上層則是針對不同平臺做一層封裝,封裝層特別要考慮的是平臺的開發慣例。由於對接SDK的開發者通常是專注於某一特定平臺的開發,不符合本平臺介面命名風格,設計慣例的介面會給他們帶來額外的學習成本,增加對接難度。

  3. 升級擴充套件
    介面設計考慮升級擴充套件這一點對每一個負責持續迭代的產品的程式設計師來講都是基本的要求。但是對於SDK產品,這一點顯得尤為重要。原因在於,一般介面設計如果不合理可以通過後期迭代重構來補救,而SDK一旦釋出之後,想要客戶修改程式碼來升級SDK的成本是很高的。介面相容性如果做的不好,客戶就不會願意升級,隨之帶來的問題是維護多個版本給我們自身帶來的巨大的支撐壓力。

  4. 註釋說明
    註釋和介面是SDK的一體兩面,它在SDK中有著和介面同等重要的作用。SDK沒有一個介面是多餘的,同樣,沒有一個介面是可以被允許沒有註釋的。我們在開發SDK時,始終要預設這樣一個前提:開發者不會看我們的整合文件。因此,我們只有儘可能的豐富註釋,才有可能最大程度將問題消滅在開發者翻閱文件或是提問之前。

SDK介面設計規範

針對上述幾個注意點,我們基於通用編碼規範,平臺命名規範,以及行業通用慣例制定了自己的SDK介面設計規範。目前還沒有特別完善和細化,但有了一個統一的標準之後,相信對未來的介面設計會有一個原則性的指導。我們的SDK主要是針對移動端iOS/android平臺,採用的語言主要是oc/java,其他平臺的設計規範可以做一個參考。

類/介面

類/介面的命名必須使用名詞。

  • android
    介面需要加I字元來區分。
// good case
public interface AliyunIRecorder;   // android; 介面加I區分

// bad case
public interface AliyunIImport;     // android; import不是名詞
複製程式碼

方法/函式

方法/函式名足夠清晰易懂,除了行業內通用的專有名詞,其他情況下不能使用縮寫。

// good case
void setExposureCompensationRatio(float value); // android; 方法名即解釋


// bad case
- (int)removeTransitionAtIndex:(int)clipIdx;    // iOS; 引數名應該使用全稱clipIndex
複製程式碼

引數儘可能少,超多4個引數需要考慮採用結構體/類封裝。

採用結構體/類封裝的好處除了減少方法長度,更重要的意義在於未來版本介面功能升級只需要新增配置屬性,而不用提供新的介面。

// bad case
void setMusic(String path,long startTime,long duration);        // android; 使用music類封裝
複製程式碼

能採用同步介面的儘量不要用非同步,非同步介面需要在註釋裡說明。

設計同步介面的行為是明確的,設計非同步介面可能讓開發者誤以為介面行為已經完成,從而做出一些錯誤呼叫。非同步介面必須強調說明,並註明對應的回撥方法是什麼。

// bad case
int finishRecording();     // android; 非同步介面需要說明對應的回撥方法
複製程式碼

一個介面只做一件事,使用doSomething命名。

// bad case
int editCompleted();            // android; 改為stopEdit/finishEdit
複製程式碼

列舉

  • iOS
    採用平臺慣例的駝峰命名法,使用typedef NS_ENUM()語法定義。
// good case
typedef NS_ENUM(NSInteger, AVCaptureFlashMode) {
    AVCaptureFlashModeOff  = 0,
    AVCaptureFlashModeOn   = 1,
    AVCaptureFlashModeAuto = 2,
}                        //iOS; 系統api命名

// bad case
typedef enum : NSUInteger {
    DIRECTION_LEFT = 0,
    DIRECTION_RIGHT = 1,
    DIRECTION_TOP = 2,
    DIRECTION_BOTTOM
} DIRECTION_TYPE;        //iOS; 沒有使用駝峰命名,列舉值命名不規範,未使用oc宣告規範
複製程式碼
  • android
    採用平臺慣例的命名方法,列舉成員名稱需要全大寫,單詞間用下劃線隔開。
// good case

public enum FormatStyle {
    FULL,
    LONG,
    MEDIUM,
    SHORT;
}              // android; 系統api命名方法

// bad case
public enum AlivcLogLevel {
    AlivcLogLevelDebug(0),
    AlivcLogLevelInfo(1),
    AlivcLogLevelWarn(2),
    AlivcLogLevelError(3),
    AlivcLogLevelFatal(4);
}              // android; 列舉值命名不規範
複製程式碼

成員變數/屬性

  • iOS
    只有在同時需要getter/setter方法的時候暴露屬性,否則使用方法代替。
    使用oc基本資料型別,不要使用c/c++的基本資料型別。

  • android
    不要直接暴露屬性,提供getter/setter方法。
    Param類必須有構造器。

// bad case
public int videoWidth;                          // android; 提供getter/setter方法
@property (nonatomic, assign) int bitrate;      // iOS; 使用NSInteger代替int
複製程式碼

命名慣例

獲取已經存在的物件使用get命名。

// bad case
AliyunICanvasController obtainCanvasController(Context var1, int var2, int var3);  // android; 使用get代替obtain
複製程式碼

獲取一個new的新物件使用create命名。

create可以給開發者暗示資源的是被建立出來的。

// good case
public static AliyunIEditor createAliyunEditor();  // android; 工廠方法使用create建立新例項

// bad case
AnimPlayerView newPasterPlayer();              // android; 使用create代替new
複製程式碼

引數設定類/結構體命名需要宣告用途,以Param結尾。

字尾可以是Config,Setting等等,一旦確定好需要所有介面保持統一。

// good case
public class AliyunVideoParam;    // android; 命名符合規範

// bad case
public class CropParam;         // android; 沒有字首
public class MediaInfo;         // android; 沒有宣告是錄製引數,沒有以Param結尾
複製程式碼

設定效果:setXxx,移除效果:clearXxx

// bad case
int addAnimationFilter(EffectFilter var1);                  // android; 不符合規範
- (void)deletePaster:(AliyunEffectPaster *)paster;          // iOS; 不符合規範
複製程式碼

新增:addXxx,刪除:removeXxx,清空:clearXxx

// good case
int addMediaClip(AliyunClip clip);        // android; 替換為clearPartList

// bad case
void deletePart();                        // android; 替換為removePart
void deleteAllPart();                     // android; 替換為clearPartList
複製程式碼

陣列/字典命名使用變數名+List/Map/Array/Dictionary

之所以不用複數形式定義變數名,主要考慮到我們開發團隊對名詞的複數形式並沒有深入掌握。與其命名不專業的變數名,不如用這種方式更為直觀。

// good case
public List<Frame> getFrameList();         // android; 符合命名規範   

// bad case
@property (nonatomic, copy) NSArray<AliyunFrameItem *> *frameItems;  // iOS; 替換為frameItemArray
複製程式碼

回撥

回撥方法除非特殊需求一般在主執行緒回撥,回撥介面註釋必須說明在哪個執行緒。

保證開發者的所有介面呼叫都在主執行緒執行可以避免很多執行緒問題。
有一些回撥需要在特定執行緒如渲染回撥等需要註釋說明。

  • iOS
    回撥類統一以Delegate結尾,會調方法以回撥類名+Did開始,一個回撥對應一個delegate屬性。

  • android
    回撥類統一以Callback結尾,會調方法以on+回撥類名開始。

// bad case
@protocol AliyunIPlayerCallback 

@end                              //iOS; 不能使用callback結尾             
複製程式碼

初始化/反初始化

主要模組必須通過init方法並使用Param配置引數初始化。

統一的初始化方式能讓開發者快速熟悉SDK呼叫方式。

  • iOS
    oc類不應提供反初始化方法。有可能的條件下,儘量按以下優先順序,在特定位置構造與析構native資源:
  1. 呼叫關鍵方法時構造native資源,完成回撥後內部析構native資源。
    例如:呼叫transcode方法構造資源,transcode完成回撥後析構資源。

  2. 對外提供成對方法處構造與析構native資源。
    例如:startPreview與stopPreview,startEdit與stopEdit,open與close。

  3. 以上都不合適,考慮在dealloc處內部析構native資源。

// bad case
- (void)destroyRecorder;              // iOS; 考慮是否可以通過1,2,3條避免
複製程式碼
  • android
    主要模組類一定要有release方法,release刪除底層資源與java同生命週期。
// bad case
void dispose();             // android; 沒有使用release()方法
複製程式碼

字首

類,介面,結構體,列舉值必須加字首。

java雖然有包名區分不同類,但還是建議加字首,這樣可以讓開發者更容易區分SDK介面。

包名

安卓所有SDK介面必須在統一的包名內。

註釋

對外介面使用/** */註釋,不要使用//註釋。

很多介面需要多行註釋才能解釋清楚,所以在這裡統一使用多行註釋。

來源:本文為第三方轉載,如有侵權請聯絡小編刪除。


相關文章