在程式開發的過程中,相同的功能往往有不同的實現方式。對於可以實現同樣功能的不同程式碼,複雜度是用於比較其質量優劣的重要指標。
在本文中,程式碼複雜度是指程式碼被理解/修改的難易程度。越容易被理解、修改的程式碼的複雜度越低;反之其複雜度越高。
複雜度低的程式碼比複雜度高的程式碼有更多好處,比如,
- 從程式碼“查邏輯”變得簡單
- 可以節省修改的時間
- 降低在未來引入bug的機率
- 新人會更容易上手現有程式碼
- 幫助整個系統更加“長壽”
ABAP開發是在SAP系統中進行的,而SAP是企業的核心資訊系統,其中會包含複雜的業務邏輯,通常由ABAP實現,並需要長期的維護。在這樣的工作中,ABAP程式碼的複雜度對系統維護成本甚至專案的成敗有著重要的影響。
在下文中,我會介紹幾種有助於最小化程式碼複雜度的通用思路,並嘗試把它們和實際的ABAP開發工作結合起來,幫助理解。
作者水平有限,如果讀者發現了任何問題,歡迎評論指出。
本文連結:https://www.cnblogs.com/hhelibeb/p/10871392.html
原創內容,轉載請註明
1, 模組化設計
兩年前,我第一次參與的SAP專案剛完成不久。那時我對自己的技術相當自信,在專案中,我不僅完成了多種型別的功能開發,而且也讀完了整個專案的新開發程式碼。即使對於一些沒做過的新的功能需求,我也往往能在不依賴乙方同事的情況下獨立查詢資料完成。自信滿滿的我決定換份新工作,並得到了一個面試機會。第一輪面試考察的是一些常用功能的實現和對業務流程的瞭解程度,如自己預想的那樣,我順利通過。在第二輪面試中,對方問到"對模組化的理解和實踐",這是個出我意料的問題,我努力地思考了一番,卻不知道該說什麼,於是被客客氣氣請了出去…
經歷了這次失敗後的我,不斷地思考著模組化設計。如果再次面對那個問題,也許我應該這樣回答:
定義
模組是系統中獨立的、可替代的單元。模組化設計,即是把系統分解為模組的集合。模組的形式多種多樣,可以是form、method、function Module、class、或report等。在理想的世界中,每個模組都完全獨立於其它模組:開發者在任何模組中工作的時候,都不需要知道有關其它模組的知識。在這種理想狀態下,系統複雜度取決於系統中複雜度最高的模組。
當然,實踐與理想不同,系統模組間總會多少有些依賴。當一個模組變化時,其它模組可能也需要隨之而改變。模組化設計的目標就是最小化模組間的依賴。
為了管理依賴,我們可以把模組看成兩部分:介面和實現。
介面包含了全部的在呼叫該模組時需要的資訊。介面只描述模組做什麼,但不會包含怎麼做。
完成介面所做出的承諾的程式碼被稱為實現。
示例
以function module為例,在function module編輯器中看到的function module名,和前6個標籤,加上function module文件(如果有),都屬於它的介面。而source code中的程式碼,則屬於實現。如下圖
進行模組化設計,是減小程式碼複雜度的第一步。因為,開發者在進行模組內部開發時,只需要關注 當前模組的介面+當前模組的實現+其它相關模組的介面。他只需要關注整個軟體系統的一小部分,接觸的東西變少,會使理解工作內容的速度大大增加,也會使犯錯的機會變小。對於試圖理解當前系統的部分功能而不做修改的人,這種設計同樣會減輕人們的負擔,因為人們通常只需要關注模組們的介面,以此瞭解程式的功能。
2, 減少異常的影響
經驗較淺的程式設計師容易犯的一個錯誤是,只考慮程式中的正常情況,即所謂的happy path,而沒有(足夠多地)考慮異常情形。不周全的考慮可以讓程式設計師快速完成功能,但是接下來則會導致測試中的頻繁翻車,程式設計師不得不再對程式進行種種的修補,導致程式碼整體的複雜度迅速升高。此外,即便是在一開始已經考慮到了各種異常情形,為了處理它們,也會給程式增加一定的複雜度。本節內容的主題是,如何合適地儘量減小由異常情形引起的複雜度。下面介紹具體的三種辦法,
從概念上消除異常
第一種辦法是從概念上消除異常。異常是相對正常而言的,功能的定義可以影響到異常的定義。ABAP SQL有插入語句,程式碼如下,
INSERT ztable FROM TABLE @lt_something.
但是,開發者通常不得不考慮主鍵重複引起的異常處理。所以在這一語句後面,還要檢查sy-subrc返回值,根據判斷做進一步處理...程式碼因此變得複雜。
如何避免此處的異常處理?假設我們對功能的設定做出一些改變,從“把內表的資料插入資料庫”改為“保證資料庫中存在內表的資料”,在這個新功能的內部判斷插入語句的執行情況,如果因為主鍵重複導致插入失敗,則改為按主鍵更新資料庫表,此時不再需要在呼叫時進行相關異常處理。
說到這裡讀者已經知道,這個“新功能”就是ABAP中的關鍵字MODIFY,
MODIFY ztable FROM TABLE @lt_something.
使用MODIFY而不是INSERT的話,一種常見異常的定義便消失了,程式碼的總量和複雜度因此會減少。當然,前提是MODIFY的功能和需求相匹配。有些資深開發者因為害怕新人不瞭解MODIFY的原理而禁止他們使用這個關鍵字,是因噎廢食的做法。
隱藏異常
把異常隱藏在較低層面是第二種做法,這種做法可以使高層的程式碼在不需要了解異常存在的情況下進行工作,從而減少高層的複雜度。SAP系統中的一個例子是tRFC。
對於tRFC而言,遠端系統不需要在RFC客戶端程式執行tRFC的時候可用。tRFC元件將被呼叫的RFC函式和相關資料儲存在SAP系統的資料庫裡,包含一個唯一的事務識別符號(transaction identifier,TID)。如果呼叫傳送了,接收系統卻是當機狀態,呼叫會保留在本地佇列中一段時間。呼叫對話程式可以在不等待遠端呼叫成功/失敗的情況下繼續執行。如果接收系統在一段時間後仍然不可用,呼叫將被計劃為後臺作業執行。
在tRFC的例子中,高層呼叫者不需要了解對方系統的狀態,也不需要進行傳輸失敗的處理,這一切都由低層完成了。高層程式的複雜度也會因此得到控制。
聚合異常
與其分散地處理程式中不同部分產生的異常,不如把它們集中交給高層的程式,進行統一的處理。SAP系統中的一個例子是BAPI。絕大多數BAPI使用一個名為RETURN引數返回所有的錯誤,這樣一來就可以由高層呼叫者對可能產生的錯誤進行統一的處理,而不是在產生錯誤的地方進行分散、個別的處理。
3, 純函式
“Hi 氫氦,我在測試中遇到了這個錯誤訊息,麻煩你看一下原因。”
“什麼?這和我們的修改毫無關係,這個報錯屬於B功能,而你知道我們改的只是程式的A功能,!”
“是的,但是我得保證這次修改沒有影響到B功能,所以請你除錯調查原因。”
測試的一個難題是測試者不知道看似單純的修改會帶來什麼樣的複雜問題,於是只好求助於人工檢查,開發者往往就是那個不幸的工人,使用純函式可以幫助避免這類情況的發生。
定義
最近流行的函數語言程式設計十分強調純函式的概念。純函式是指符合以下條件的函式,
- 對於相同的輸入,函式總有相同的輸出。
這要求函式內部不能存在“副作用”。
它的輸出結果的確定不應該依賴輸入引數外的任何內容,例如,不可以因為本地測試環境中沒有相應的資料庫就產生“連線資料庫異常”導致無法返回結果。
它也不應該改變除了返回結果以外的任何內容,例如,不可以改變全域性可變狀態。
滿足以上條件的函式,可以被稱為純函式。
從模組化的角度來看,全域性狀態和對外部系統的連線都屬於介面的一部分。純函式不會與這些東西產生互動,因此它的介面會更簡單,複雜度更低。
雖然ABAP不是函式式語言,但它依然可以有純函式,並且開發者可以通過寫純函式而受益。
在上面的例子中,如果開發者可以證明A、B功能分屬2個模組,而且它們都屬於純函式,那麼只要證明A的變更不會改變B的輸入,即可證明修改沒有導致對方給出的錯誤。
4, 需求文件與實現
系統中的模組可以分為介面和實現,換一個角度思考的話,程式碼也可以看作需求文件的實現。需求語言的準確性,對程式碼產物的質量有著直接的影響。在SAP開發中,業務邏輯的實現是首要目標,業務複雜度也往往是程式碼複雜度的最主要來源。
資訊丟失
程式是需求的實現,因此程式碼應當儘量包含需求文件中的資訊。在需求文件的質量可靠的前提下,這樣做可以有效提高程式的可讀性,從而降低其複雜度。資訊丟失的一個極端例子是程式碼混淆,顯然混淆後的程式碼複雜度將大大升高。
當然,程式語言和系統內部的資訊和需求文件中的自然語言是有差別的,這也是程式設計師存在的意義。在實際的開發中,可以嘗試通過增加一個抽象層的方式來保留需求文件中的資訊。比如,在高層對需求語言進行建模,在低層實現實際的執行過程。
注意:如果需求文件很長,程式碼實現很少的話,意味著工作可能存在某些問題,可能是需求內容冗餘、功能設計不合理、實現不完整、資訊丟失等,此時要重新思考相關工作內容,以確保未出現這些問題。
詞彙表
保留資訊的前提條件是明確資訊,需求文件中應當有詞彙表,幫助開發人員迅速捕捉和理解最重要的業務語言,以便把它們落實在程式中。
對於在中文環境下的工作而言,這點尤其重要。ABAP並不適合使用中文命名,而普通的程式設計師沒有能力把中文的業務語言轉換成英文的,需要由業務顧問來完成這項意義重大的工作。
5,工作轉移
最後,還有一種顯而易見的辦法是將某些業務邏輯轉移到ABAP之外實現,比如下推到資料庫層面、使用配置工具實現(BRF+)、交給中介軟體等。
優點:從ABAP角度來看,這種辦法最有效地避免了複雜度的增加。
缺點:從全域性來看,複雜度並沒有真正消失,只是隨著工作量轉移到了另一個地方。
要從系統的整體複雜度的角度來考慮實現邏輯的位置,比如,人們常常使用配置表配合ABAP程式碼來實現自定義邏輯,BRF+中的decision table可以實現相似的功能,如果需求希望得到使用者在實際使用程式時對不同邏輯的命中率的話,那麼decision table可能會更合適一些,因為BRF+中包含跟蹤模式,可以被直接利用,這樣就可以通過寫簡單的程式碼來得到結果,避免引入更多的複雜度。
後記
長期關注本部落格的讀者可能會注意到,這個部落格已經有段時間沒有更新ABAP相關內容,這是因為從今年開始,我的工作重心已經逐漸轉移到Spark和Dynamics等其它方面的開發,花在SAP上面的時間變得很少。恰好最近參加了一個新的SAP專案,它喚醒了我對ABAP的一些記憶,於是趁熱打鐵,寫了這篇文章。這篇文章算是在ABAP角度上將最近學習到的東西進行的一個總結複習。希望它能對讀者有所幫助,也真誠地希望能收到反饋,共同討論相關話題。
參考連結:《A Philosophy of Software Design》