1. 程式人生 > >開發小結-業務管理類

開發小結-業務管理類

else if 設置字體 改變 isa 思考 過程 可控 pro struct

本篇文章關註於編程實踐中的相關流程設計內容,內容來源自己過去的工作總結。

業務流程設計

越復雜流程,越容易出錯。為了減少出錯的情況,需要提取並且封裝通用邏輯,用一個易於理解的名字來對外提供服務。在業務不同抽象層級上,幹各自職責對應的事情。

前後臺交互的流程越多,需要維護的狀態就越多,出現問題的概率就越大,因此在不影響主要功能的前提下,流程能簡化就盡量簡化,那些被簡化的路徑,在某些異常場景下,會對業務有一定的影響,具體影響到什麽程序,在上線前,誰也不好說。可通過數據埋點得到上線後的真實數據,以供權衡。

針對復雜業務時,不能一上手就開始寫,要先設計好框架和整體流程。對於一些需求上面沒有明確的交互細節,一般按照通用交互細節來就可以。

對於具體子功能的實現上,需要細致考慮,較好的實踐方法是逐條列出來,在每種狀態或情況下,什麽樣的輸入得到什麽樣的輸出。

在復雜程序執行過程中,需要維護多個狀態,隨著業務的改變,在處理於狀態關聯的事件時,思維負擔會越來越重重,很容易顧此失彼。
為了更好的控制復雜度,簡化邏輯,需要對狀態進行分級管理,將每個級別的狀態和對應處理分層化。對外統一狀態操作接口,每個模塊中,都通過統一狀態接口來管理狀態。

每一個常量數字,在業務上都要有依據來源,要麽是經驗值,要麽是業務規則規定的。

失敗流程

任何失敗重試類操作,都要考慮對後臺的影響。因為網關只會轉發請求,大量冗余的請求會給後臺造成巨大壓力,可能會影響正常業務流程。對於失敗重試,一定要設置定時間隔以及重試次數,這裏的失敗要區分類型,如果是業務操作類的失敗,業務層直接提示出錯即可。如果是其他類型的失敗,底層需要做好重試策略,直到重試策略返回失敗,才可認為是失敗,此過程對上層是透明的。

針對開發回發的數據,要進行有效性校驗才能繼續往下走,在有些業務場景中,需要對不同類型的失敗分配不同類的錯誤碼,以便外部處理。比如讀取配置信息這個功能,可能的出錯就有以下幾種情況:

  1. 配置文件不存在
  2. 配置文件存在,但文件格式不正確
  3. 配置文件存在,文件格式正確,屬性Key缺失
  4. 配置文件存在,文件格式正確,屬性Key正常,屬性Value缺失
  5. 配置文件存在,文件格式正確,屬性Key正常,屬性Value是異常值(超大的數,或者是負數)

如果因為上述種種原因,導致讀取配置文件失敗,那要做好默認配置的處理,根據產品要求,對不同的異常情況,做出對應的處理,使用默認配置繼續運行,還是彈框提示用戶等。

在對後臺返回來的格式化數據進行解析時,一定要考慮對應字段不存在的情況下該如何處理。不管後臺返回什麽類型的錯誤數據,都不能崩潰。

流程優化

仔細分析業務流程,避免不必要的依賴和操作,相關的業務邏輯,要聚合到一起。

分析問題的思路要不斷在實踐中打磨,反思,總結。自己第一時間想到的方案,往往還有很大的優化空間,那麽,從什麽角度去思考優化呢?在開發實踐中,逐漸明確兩個大的方向,在此記錄一下

  1. 從底層往上層思考 : 底層是最基礎的數據層,業務流程的數據是不是可以往下移動到底層,然後調用底層明確性語義接口函數來實現上層業務,而不是把一堆判斷邏輯都交由業務層去做。

  2. 從上層往底層思考 : 在業務層和底層數據之間,要有一個間接層,對上提供較為高級的功能,對下封裝最底層數據,提供更高層次的接口。

  3. 相似的業務處理采用相似的辦法,不要一堆條件,範圍A-C的用這樣的處理方法,業務範圍D-F的用那樣的處理方法,其他類型業務用其他的處理方法。這樣一來,業務和對應的錯誤處理分散在各處,後期維護很容易出錯。

一個類,如需向其他類獲得相關信息,不要依賴於類與類之間的繼承組織關系,嘗試通過this來進行強制轉換來獲取,因為這種層級關系在未來可能會發生變動,而這種層級關系的變動,不會通知使用者,因此在實現類似需求時,需要將對外部的依賴關系盡可能的降低,建議采用消息的方式,每層規定好消息的職責,一層一層向上傳遞請求,直到某一層給與響應為止。

一致性

一致性,指的是對於UI元素和代碼命名的一致性,同一邏輯元素的命名一致性,它在UI層、中間層、網絡層以及後臺接口中的核心詞匯要保持一致,這樣增加代碼的可讀性。

一致性體現在資源管理上,同一份資源,誰申請,誰就負責釋放。誰增加引用記數,誰就負責減少。

在一些通用功能的設置上,保持代碼一致性很重要。比如設置控件字體,原生系統會提供一套接口,自繪部分也會提供一套接口,那麽外部在使用時,就會有疑惑,設置字體是用原生接口還是自繪接口呢?他們會不會相互影響呢?在前期設計時,同一類功能,只提供一套接口較好。

相似操作能夠歸集到一起的,一定要歸集到一起,讓邏輯聚合,而不是到處分散。

修改影響評估

比如,你修改了A函數中的A分支,A函數的B分支沒有改動。外部O1模塊使用了A函數的B分支,O2模塊使用了A函數的A分支,那麽你的本次修改涉及到的影響範圍是哪些呢?O2模塊肯定是要測的,O1模塊需不需要提測?答案是需要的,從廣義上來看,任何調用A函數的模塊都要重測一片。即使本次修改沒有涉及到分支,也要去測試。

在嘗試修改後臺程序時,要註意與之前系統的兼容性。

在修改一個涉及範圍比較大的問題時,如果涉及的地方太多了,為了最終問題的解決和測試方面的平滑過渡,需要將問題分治,按照功能需求模塊來統計匯總,然後,將問題列表按模塊分配給各自負責人,由他們去進一步往下分散進行修改,這樣可以保證大型問題的平滑過渡,為開發和測試提供緩沖時間段。

比如本次修改點可分為以下幾個集合,

集合1:涉及到哪些方面

集合2:涉及到哪些方面

集合3:涉及到哪些方面

邏輯完備性

對於層級調用,資源的申請和釋放,要特別註意一致。這一點,特別容易遺漏。
比如說,先壓入緩存,再發出請求,那麽,清理緩存的時機,應該要覆蓋發出請求後的每一條執行路徑。
7.1 發出請求失敗
7.2 發出請求成功,響應成功
7.3 發出請求成功,響應失敗

一段業務代碼,正確的路徑只有一條,但錯誤的路徑有很多條,怎麽處理在這個過程中有可能出現的錯誤,就很顯的功力所在了。

如果遇到一些需要特殊處理的地方,優先把大部分情況放在主幹邏輯上,在額外的分支中處理額外。

凡是涉及到網絡請求的,如果中途關閉了頁面,一定要註意請求資源的清理操作,這樣在重新打開頁面,不會收到上一次無效的響應。

優化前的準備

沒有profile的優化都是瞎扯淡,一旦開始重構,就要指定好優化目標,所有的相關修改都聚焦在目標上,不能跑偏了。優化前要分析現狀、制定方案和可量化的目標,完成優化後,需要提取相關的數據,總結對比可量化的分析優化成果。後臺重構需要配置A/B方案,對於可能會有變化的模塊或者業務,或者A、B方案不好取舍的,可采用後臺配置采用哪個方案,當重構的版本上線時,先保留舊的模塊,如果有問題,可以在後臺中配置切回到舊的模塊,這種思想同可以同灰度發布一起使用(5W,10W)

在進行組件的重構時,需要遵循最小影響範圍來做,一處重構,盡量只影響到這個業務,不影響到其他業務,保證可控性和可測試性。

維護經驗

對於接手舊項目,裏面用到的第三方庫,如果有新版本的接口庫或者更好的第三方接口庫,該如何抉擇?

  • 如果原有的庫能夠滿足需求,則不要貿然替換或者升級
  • 如果原有的庫不能滿足特定場景下的需求,優先自己封裝在特定場景下的接口,以供使用。如果特定場景下的需求需要的新功能不能通過封裝已有API來提供,而該功能在原有庫的升級版中存在,那麽需要和原有庫維護者商量討論後再進行升級。
  • 用新的三方庫來替換,該方法改動範圍會很大,不到萬不得已,不允許使用。

當老舊代碼中有很多相互交纏的邏輯,需要改動多處才能改好詞Bug,不好評估改動影響的範圍,這個時候,為了最大限度的兼容老舊代碼,不能繼續在老舊代碼的基礎上繼續填坑、挖坑,如果選取其中一個簡單的界面,用清晰簡單的邏輯去實現,然後在逐步替換老舊模塊,小步慢跑的改進。

當需要在原有功能上面增加新的功能時,要特別註意一點,就是原有功能所使用的指令和流程不能更改。比如查詢A產品的天數信息,原有的指令只支持一種產品的查詢,現在其他界面有同時查詢多種產品的情況,而原有指令只實現了查詢單個產品信息, 那麽,下面有兩種解決方法:

                                               優點                       缺點
方法一:修改原有指令,使其支持多條信息的查詢      增加指令的功能         需修改原有查詢單條產品的頁面,增加了測試成本
方法二:不改動原有指令,新增支持多條信息查詢指令  不會影響已有功能       類似功能有多處實現,查多條包含查一條的功能

因為查詢一條產品信息屬於查詢多條產品信息的特例,為了可維護性,采用方法一較好。

一個簡答的權限判斷功能設計

有A、B、C三個賬號,有ID1,ID2,ID3,ID4四個功能,每個賬號允許的功能如下:

A--> ID1, ID3
B--> ID2, ID3
C--> ID4

現在要設計一個權限判斷的函數,給定功能ID和賬號,返回是否允許執行此操作判斷?

一種是從上往下,從入口ID和各個可操作ID段來判斷,優點是直觀,簡單,缺點是對擴展性不好,未來要增加新的入口時,需要修改多出
方法一: 從上到下,邏輯簡單直接,可擴展性差

bool IsAllow(Account acnt, int nFunId)
{
    if (A == acnt)
    {
        if (nFunId == ID1 || nFunId == ID3)
        {
            return TRUE;
        }
    }
    else if (B == acnt)
    {
        if (nFunId == ID2 || nFunId == ID3)
        {
            return TRUE;
        }
    }
    else if (C == acnt)
    {
        if (nFunId == ID4)
        {
            return TRUE;
        }
    }
    
    return FALSE;
}

另一種方法是從下往上,將每個交易ID和能夠操作條件集合在一起,這種方式的擴展性和可讀性都非常好,推薦使用。

struct TIDInfo
{
    int nId;
    vector<Account> vAllowAcntType;   //  該ID要求的賬號屬性
    
    bool IsAllow(Account acnt)        // 給定賬號是否存在特定屬性
    {
       return vAllowAcntType.find(acnt) != vAllowAcntType.end();
    }
}

vector<TIDInfo> allIdInfo;            // 所有功能ID集合
bool IsAllow(Account acnt, int nFunId)
{
    for (int i = 0 ; i < allIdInfo.size(); ++i)
    {
        if (allIdInfo[i].nId == nFunId)
        {
            return allIdInfo[i].IsAllow(acnt);
        }
    }
    
    return FALSE;
}

開發小結-業務管理類