厭倦了混亂的代碼?掌握編寫干凈代碼庫的藝術
譯文譯者 | 李睿
審校 | 重樓
對于入門的開發人員來說,雖然克服了最初的障礙,學會了編程,找到了理想的工作。但其編程旅程并沒有就此結束。他們面臨真正的挑戰:如何編寫更好的代碼。這不僅僅是為了完善功能,還要編寫出經得起時間考驗的優雅、可維護的代碼。
在設計糟糕的軟件系統中,開發人員在后臺就像迷失在一個沒有地圖導航的城市里一樣。這些系統往往笨重、低效且令人沮喪。
開發人員可以通過設計更好的以用戶為中心、高效、簡單、靈活的系統來改變這種狀況。他們可以使用函數、變量、類和注釋來編寫“不要重復自己”(DRY)和模塊化的代碼,設計為人們服務的系統,而不是相反。
因此開發人員的選擇是明確的:編寫賦能的代碼而不是阻礙的代碼,構建讓開發人員茁壯成長的代碼體系。
代碼庫的挑戰
想象一下,如果你是一名銷售主管,采用了一款新的應用程序,旨在簡化工作流程,并最大限度地提高潛在客戶的轉化率。這款應用程序由SmartReach API提供支持,可以實時了解潛在客戶,并有可能獲得獎勵。
但感覺這款應用程序有些問題,其代碼缺乏清晰度和結構,引起了人們對其準確性和功能的擔憂。分析代碼變成了一項令人困惑的任務,阻礙了有效使用應用程序的能力。
與其糾結于晦澀難懂的代碼行,不如以一種清晰而結構化的方式來分解代碼。這將使開發人員能夠識別潛在的錯誤,并確保應用程序順利運行,最大限度地提高生產力和獲得獎勵的潛力。
class ApiCall {
def getData(pn: Int, v: Int) = Action.async(parse.json) { request =>
ws.url(s"https://api.smartreach.io/api/v$v/prospects?page=$pn")
.addHttpHeaders(
"X-API-KEY" -> "API_KEY"
)
.get()
.flatMap(r => {
if (r.status == 200) {
Res.Success("Success!", (response.json \ "data").as[JsValue])
} else {
Res.ServerError("Error!",new Exception((response.json \ "message").as[String]))
}
})
}
}
我們將重構一個用Scala編寫但適用于一般編碼的示例銷售應用程序。那么目標是什么?是將令人頭疼的混亂代碼變成一部清晰可讀的杰作。
以下分解重構過程,解釋每個步驟并展示其積極影響。讓我們一起創造激勵和賦能的代碼!
代碼重構
命名約定和代碼注釋
如今,我們都在關注快速消息,但在編寫代碼時,這并不奏效。
為變量、類、函數和對象等使用清晰的名稱是非常重要的。如果讓同事幫助檢查或修改代碼,別忘了在代碼中編寫一些注釋。這可能看起來沒什么大不了的,但當你或你的同事試圖解決出現的代碼問題時,這些注釋將提供很大的幫助。
讓我們看看遵循這些規則之后編寫代碼的結果:
class ProspectsApiGet {
def getProspectListFromSmartReach(
page_number: Int,
version: Int
) = Action.async(parse.json) { request =>
//public API of SmartReach to get the prospect list by page number
//smartreach.io/api_docs#get-prospects
ws.url(s"https://api.smartreach.io/api/v$version/prospects?page=$page_number")
.addHttpHeaders(
"X-API-KEY" -> "API_KEY"
)
.get()
.flatMap(response => {
val status = response.status
if (status == 200) {
//if the API call to SmartReach was success
/*
{
"status": string,
"message": string,
"data": {
"prospects": [*List of prospects*]
}
}
*/
val prospectList: List[JsValue] = (response.json \ "data" \\ "prospects").as[List[JsValue]]
Res.Success("Success!", prospectList)
} else {
// error message
//smartreach.io/api_docs#errors
val errorMessage: Exception = new Exception((response.json \ "message").as[String])
Res.ServerError("Error", errorMessage) // sending error
}
})
}
}
如果仔細觀察,錯誤就會隱藏在代碼中! 對潛在客戶的分析不太正確。但是,通過提供更多的注釋和使用更好的名稱,可以避免這些錯誤。
代碼庫組織和模塊化
直到現在都一切順利。編寫的代碼很好,應用程序也很簡單。既然代碼運行良好并且更易于閱讀,那么讓我們來處理下一個挑戰。
考慮在這里引入一個篩選系統。這會讓事情變得更加復雜。我們真正需要的是一個更有組織的代碼結構。
為了使其實現模塊化,我們將代碼分成更小的模塊。但是如何確定每個模塊的位置呢?
(1)目錄結構
目錄是代碼的總部,本質上是一個將所有內容放在一起的文件夾。如果創建一個新文件夾,這樣就有了一個目錄。
在這個目錄中,可以存儲代碼文件或創建其他子目錄。在我們的例子中則選擇子目錄。將代碼分成四個部分:模型、數據訪問對象(DAO)、服務和控制器。
值得注意的是,目錄結構可能會根據企業偏好或應用程序的特定需求而有所不同。你可以根據最適合企業或應用的內容來定制。
(2)模型
當我們談論編碼中的模型時,本質上是在談論用來構建和管理數據的框架。例如在這個例子的場景中,前景模型充當了藍圖,概述了代表系統內前景的特定特征和行為。
在這個模型中,我們定義了潛在客戶擁有的屬性——可能是姓名、聯系方式或任何其他相關信息。這不僅僅是存儲數據,還是有關以一種對應用程序有效地交互和操作有意義的組織方式。
case class SmartReachProspect(
sr_prospect_id: Long, //id of the prospect in SmartReach database
p_first_name: String,
p_last_name: String,
p_email: String,
p_company: String,
p_city: String,
p_country: String,
smartreach_prospect_category: String // Prospect category in SmartReach
// can be "interested", "not_interested", "not_now", "do_not_contact" etc
)
(3)數據訪問對象(DAO)
這個對象被恰當地命名為數據訪問對象(DAO),充當從數據庫或第三方API獲取數據的橋梁。
避免在這些文件中添加復雜的邏輯是至關重要的;它們應該只專注于處理輸入和輸出操作。當我們談論IO操作時,指的是與外部系統的交互,其中故障的可能性更高。因此,在這里實現保障措施對于處理意外問題至關重要。
在Scala編程語言中,我們利用Monad(特別是Futures)來有效地管理和處理潛在的故障。這些工具有助于捕獲和管理IO操作過程中可能發生的故障。
數據訪問對象(DAO)的主要目標是從數據源檢索數據,然后將其組織到適當的模型中,以便進一步處理和利用。
class SmartReachAPIService {
def getSmartReachProspects(
page_number: Int,
Version: Int
)(implicit ws: WSClient,ec: ExecutionContext): Future[List[SmartReachProspect]] = {
//public API from SmartReach to get the prospects by page number
//smartreach.io/api_docs#get-prospects
ws.url(s"https://api.smartreach.io/api/v$version/prospects?page=$page_number")
.addHttpHeaders(
"X-API-KEY" -> "API_KEY"
)
.get()
.flatMap(response => {
val status = response.status
if (status == 200) {
//checking if the API call to SmartReach was success
/*
{
"status": string,
"message": string,
"data": {
"prospects": [*List of prospects*]
}
}
*/
val prospects: List[SmartReachProspect] = (response.json \ "data" \\ "prospects").as[List[SmartReachProspect]]
prospects
} else {
//error message
//smartreach.io/api_docs#errors
val errorMessage: Exception = new Exception((response.json \ "message").as[String])
throw errorMessage
}
})
}
}
(4)服務
這里是我們系統的核心——業務邏輯位于這一層。在這里將實現一種篩選機制,展示如何毫不費力地在這部分代碼庫中引入額外的功能。
這個部分編排驅動應用程序的核心操作和規則。在這里,根據業務需求定義應該如何處理、操作和轉換數據。讓添加新特性或邏輯變得相對簡單,可以輕松地擴展和增強系統的功能。
class SRProspectService {
val smartReachAPIService = new SmartReachAPIService
def filterOnlyInterestedProspects(
prospects: List[SmartReachProspect]
): List[SmartReachProspect] = {
prospects.filter(p => p.prospect_category == "interested")
}
def getInterestedProspects(
page_number: Int,
version: Int
)(implicit ws: WSClient,ec: ExecutionContext): Future[List[SmartReachProspect]] = {
val allProspects: Future[List[SmartReachProspect]] = smartReachAPIService.getSmartReachProspects(page_number = page_number, version = version)
allProspects.map{list_of_prospects =>
filterOnlyInterestedProspects(prospects = list_of_prospects)
}
}
}
(5)控制器
在這一層,我們與API建立直接連接——這一層充當網關。
它充當接口,通過我們的API接收來自前端或第三方用戶的請求。在接到這些請求之后,收集所有必要的數據,并在處理后處理響應。
保持關注點分離是至關重要的。因此避免在這個層中實現邏輯。與其相反,該層側重于管理傳入請求流,并將它們引導到實際處理和業務邏輯發生的適當服務層。
class ProspectController {
val prospectService = new SRProspectService
def getInterestedProspects(
page_number: Int
) = Action.async(parse.json) { request =>
prospectService
.getInterestedProspects(page_number = page_number, version = 1)
.map{ intrested_prospects =>
Res.Success("Success", intrested_prospects)
}
.recover{ errorMessage =>
Res.ServerError("Error", errorMessage) // sending error to front end
}
}
}
我們改進的代碼庫具有更高的清潔度和更強的可管理性,促進了更高效的重構工作。
此外,我們還建立了不同的標記,用于合并邏輯、執行數據庫操作或無縫集成新穎的第三方API。這些清晰的劃分簡化了在系統中擴展功能和適應未來增強的過程。
測試和質量保證
測試可能看起來是重復的,但它的重要性怎么強調都不為過,尤其是在熟練使用的情況下,無需為重新編碼而煩惱。
讓我們更深入地了解構建健壯Spec文件的指導原則。
1.覆蓋是關鍵:在為特定函數構建規范時,確保這些規范涉及該函數中的每一行代碼是至關重要的。實現全面覆蓋保證了在測試過程中對功能內的所有路徑和場景進行仔細檢查。
2.失敗優先測試:Spec文件的主要作用是檢查代碼在不同情況下的行為。為了實現這一點,包含一系列測試用例是至關重要的,尤其是那些模擬潛在故障場景的測試用例。確保可靠的錯誤處理需要對所有可預見的故障實例進行測試。
3.接受集成測試:雖然單元測試擅長于評估代碼的邏輯方面,但它們可能會無意中忽略與輸入/輸出操作相關的潛在問題。為了解決這個問題,集成和執行徹底的集成測試變得必不可少。這些測試模擬真實世界的場景,驗證代碼在與外部系統或資源交互時的行為。
協作與團隊合作
有一條要記住的黃金法則:在遇到難以解決的問題時,可以尋求他人幫助。他人的幫助對于你自身能力實現指數增長至關重要。
創建一個健壯的代碼庫不是一項單獨的任務,而是需要團隊的努力。盡管開發人員的職業表面上看起來是內向的,但協作構成了編碼的支柱。這種合作超越了企業的界限。
GitHub、LeetCode和StackOverflow等平臺展示了編碼社區的活躍和社交性質。如果你遇到一個以前沒有遇到的問題,很可能別人也遇到過。因此請始終對尋求幫助持開放態度。
代碼文檔的最佳實踐
無論是公共API還是內部代碼,可靠的文檔是游戲規則的改變者。不過,現在將重點放在了編碼方面。
確保一流的文檔實踐不僅僅是為了簡化開發人員的入職過程,而是為了向開發團隊提供一個對代碼庫有清晰見解的寶庫。這種清晰性推動了學習,使每個參與者都受益。
預先編碼和研究
這是啟動項目的所有要素的起點——從系統設計到指向相關文檔的關鍵鏈接。
在預編碼文檔中,開發人員不僅可以找到新的表結構和修改,還可以找到指向第三方文檔的有價值的鏈接。需要記住的是,充分的準備為成功奠定了基礎,將會事半功倍!
編碼期間
這是開發人員在代碼中嵌入注釋的地方。這些注釋作為指南,揭示了代碼功能背后的復雜性和意圖。
事后編碼
這是有效利用代碼的指導中心,也是將來修改代碼的指南。它是引導用戶使用代碼的指南,并概述了后續修改代碼的路徑。
結論
優秀的代碼遵循可靠的命名約定,保持模塊化,遵循“不要重復自己”(DRY)原則,確保可讀性,并經過徹底的測試。編寫這樣的代碼需要混合文檔、協作以及為編碼人員提供幫助。
文章標題:Tired of Messy Code? Master the Art of Writing Clean Codebases,作者:Upasana Sahu