厭倦了混亂的代碼?掌握編寫干凈代碼庫的藝術(shù)
譯文譯者 | 李睿
審校 | 重樓
對(duì)于入門的開發(fā)人員來說,雖然克服了最初的障礙,學(xué)會(huì)了編程,找到了理想的工作。但其編程旅程并沒有就此結(jié)束。他們面臨真正的挑戰(zhàn):如何編寫更好的代碼。這不僅僅是為了完善功能,還要編寫出經(jīng)得起時(shí)間考驗(yàn)的優(yōu)雅、可維護(hù)的代碼。
在設(shè)計(jì)糟糕的軟件系統(tǒng)中,開發(fā)人員在后臺(tái)就像迷失在一個(gè)沒有地圖導(dǎo)航的城市里一樣。這些系統(tǒng)往往笨重、低效且令人沮喪。
開發(fā)人員可以通過設(shè)計(jì)更好的以用戶為中心、高效、簡(jiǎn)單、靈活的系統(tǒng)來改變這種狀況。他們可以使用函數(shù)、變量、類和注釋來編寫“不要重復(fù)自己”(DRY)和模塊化的代碼,設(shè)計(jì)為人們服務(wù)的系統(tǒng),而不是相反。
因此開發(fā)人員的選擇是明確的:編寫賦能的代碼而不是阻礙的代碼,構(gòu)建讓開發(fā)人員茁壯成長(zhǎng)的代碼體系。
代碼庫的挑戰(zhàn)
想象一下,如果你是一名銷售主管,采用了一款新的應(yīng)用程序,旨在簡(jiǎn)化工作流程,并最大限度地提高潛在客戶的轉(zhuǎn)化率。這款應(yīng)用程序由SmartReach API提供支持,可以實(shí)時(shí)了解潛在客戶,并有可能獲得獎(jiǎng)勵(lì)。
但感覺這款應(yīng)用程序有些問題,其代碼缺乏清晰度和結(jié)構(gòu),引起了人們對(duì)其準(zhǔn)確性和功能的擔(dān)憂。分析代碼變成了一項(xiàng)令人困惑的任務(wù),阻礙了有效使用應(yīng)用程序的能力。
與其糾結(jié)于晦澀難懂的代碼行,不如以一種清晰而結(jié)構(gòu)化的方式來分解代碼。這將使開發(fā)人員能夠識(shí)別潛在的錯(cuò)誤,并確保應(yīng)用程序順利運(yùn)行,最大限度地提高生產(chǎn)力和獲得獎(jiǎng)勵(lì)的潛力。
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]))
}
})
}
}
我們將重構(gòu)一個(gè)用Scala編寫但適用于一般編碼的示例銷售應(yīng)用程序。那么目標(biāo)是什么?是將令人頭疼的混亂代碼變成一部清晰可讀的杰作。
以下分解重構(gòu)過程,解釋每個(gè)步驟并展示其積極影響。讓我們一起創(chuàng)造激勵(lì)和賦能的代碼!
代碼重構(gòu)
命名約定和代碼注釋
如今,我們都在關(guān)注快速消息,但在編寫代碼時(shí),這并不奏效。
為變量、類、函數(shù)和對(duì)象等使用清晰的名稱是非常重要的。如果讓同事幫助檢查或修改代碼,別忘了在代碼中編寫一些注釋。這可能看起來沒什么大不了的,但當(dāng)你或你的同事試圖解決出現(xiàn)的代碼問題時(shí),這些注釋將提供很大的幫助。
讓我們看看遵循這些規(guī)則之后編寫代碼的結(jié)果:
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
}
})
}
}
如果仔細(xì)觀察,錯(cuò)誤就會(huì)隱藏在代碼中! 對(duì)潛在客戶的分析不太正確。但是,通過提供更多的注釋和使用更好的名稱,可以避免這些錯(cuò)誤。
代碼庫組織和模塊化
直到現(xiàn)在都一切順利。編寫的代碼很好,應(yīng)用程序也很簡(jiǎn)單。既然代碼運(yùn)行良好并且更易于閱讀,那么讓我們來處理下一個(gè)挑戰(zhàn)。
考慮在這里引入一個(gè)篩選系統(tǒng)。這會(huì)讓事情變得更加復(fù)雜。我們真正需要的是一個(gè)更有組織的代碼結(jié)構(gòu)。
為了使其實(shí)現(xiàn)模塊化,我們將代碼分成更小的模塊。但是如何確定每個(gè)模塊的位置呢?
(1)目錄結(jié)構(gòu)
目錄是代碼的總部,本質(zhì)上是一個(gè)將所有內(nèi)容放在一起的文件夾。如果創(chuàng)建一個(gè)新文件夾,這樣就有了一個(gè)目錄。
在這個(gè)目錄中,可以存儲(chǔ)代碼文件或創(chuàng)建其他子目錄。在我們的例子中則選擇子目錄。將代碼分成四個(gè)部分:模型、數(shù)據(jù)訪問對(duì)象(DAO)、服務(wù)和控制器。
值得注意的是,目錄結(jié)構(gòu)可能會(huì)根據(jù)企業(yè)偏好或應(yīng)用程序的特定需求而有所不同。你可以根據(jù)最適合企業(yè)或應(yīng)用的內(nèi)容來定制。
(2)模型
當(dāng)我們談?wù)摼幋a中的模型時(shí),本質(zhì)上是在談?wù)撚脕順?gòu)建和管理數(shù)據(jù)的框架。例如在這個(gè)例子的場(chǎng)景中,前景模型充當(dāng)了藍(lán)圖,概述了代表系統(tǒng)內(nèi)前景的特定特征和行為。
在這個(gè)模型中,我們定義了潛在客戶擁有的屬性——可能是姓名、聯(lián)系方式或任何其他相關(guān)信息。這不僅僅是存儲(chǔ)數(shù)據(jù),還是有關(guān)以一種對(duì)應(yīng)用程序有效地交互和操作有意義的組織方式。
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)數(shù)據(jù)訪問對(duì)象(DAO)
這個(gè)對(duì)象被恰當(dāng)?shù)孛麨閿?shù)據(jù)訪問對(duì)象(DAO),充當(dāng)從數(shù)據(jù)庫或第三方API獲取數(shù)據(jù)的橋梁。
避免在這些文件中添加復(fù)雜的邏輯是至關(guān)重要的;它們應(yīng)該只專注于處理輸入和輸出操作。當(dāng)我們談?wù)揑O操作時(shí),指的是與外部系統(tǒng)的交互,其中故障的可能性更高。因此,在這里實(shí)現(xiàn)保障措施對(duì)于處理意外問題至關(guān)重要。
在Scala編程語言中,我們利用Monad(特別是Futures)來有效地管理和處理潛在的故障。這些工具有助于捕獲和管理IO操作過程中可能發(fā)生的故障。
數(shù)據(jù)訪問對(duì)象(DAO)的主要目標(biāo)是從數(shù)據(jù)源檢索數(shù)據(jù),然后將其組織到適當(dāng)?shù)哪P椭?,以便進(jìn)一步處理和利用。
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)服務(wù)
這里是我們系統(tǒng)的核心——業(yè)務(wù)邏輯位于這一層。在這里將實(shí)現(xiàn)一種篩選機(jī)制,展示如何毫不費(fèi)力地在這部分代碼庫中引入額外的功能。
這個(gè)部分編排驅(qū)動(dòng)應(yīng)用程序的核心操作和規(guī)則。在這里,根據(jù)業(yè)務(wù)需求定義應(yīng)該如何處理、操作和轉(zhuǎn)換數(shù)據(jù)。讓添加新特性或邏輯變得相對(duì)簡(jiǎn)單,可以輕松地?cái)U(kuò)展和增強(qiáng)系統(tǒng)的功能。
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建立直接連接——這一層充當(dāng)網(wǎng)關(guān)。
它充當(dāng)接口,通過我們的API接收來自前端或第三方用戶的請(qǐng)求。在接到這些請(qǐng)求之后,收集所有必要的數(shù)據(jù),并在處理后處理響應(yīng)。
保持關(guān)注點(diǎn)分離是至關(guān)重要的。因此避免在這個(gè)層中實(shí)現(xiàn)邏輯。與其相反,該層側(cè)重于管理傳入請(qǐng)求流,并將它們引導(dǎo)到實(shí)際處理和業(yè)務(wù)邏輯發(fā)生的適當(dāng)服務(wù)層。
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
}
}
}
我們改進(jìn)的代碼庫具有更高的清潔度和更強(qiáng)的可管理性,促進(jìn)了更高效的重構(gòu)工作。
此外,我們還建立了不同的標(biāo)記,用于合并邏輯、執(zhí)行數(shù)據(jù)庫操作或無縫集成新穎的第三方API。這些清晰的劃分簡(jiǎn)化了在系統(tǒng)中擴(kuò)展功能和適應(yīng)未來增強(qiáng)的過程。
測(cè)試和質(zhì)量保證
測(cè)試可能看起來是重復(fù)的,但它的重要性怎么強(qiáng)調(diào)都不為過,尤其是在熟練使用的情況下,無需為重新編碼而煩惱。
讓我們更深入地了解構(gòu)建健壯Spec文件的指導(dǎo)原則。
1.覆蓋是關(guān)鍵:在為特定函數(shù)構(gòu)建規(guī)范時(shí),確保這些規(guī)范涉及該函數(shù)中的每一行代碼是至關(guān)重要的。實(shí)現(xiàn)全面覆蓋保證了在測(cè)試過程中對(duì)功能內(nèi)的所有路徑和場(chǎng)景進(jìn)行仔細(xì)檢查。
2.失敗優(yōu)先測(cè)試:Spec文件的主要作用是檢查代碼在不同情況下的行為。為了實(shí)現(xiàn)這一點(diǎn),包含一系列測(cè)試用例是至關(guān)重要的,尤其是那些模擬潛在故障場(chǎng)景的測(cè)試用例。確??煽康腻e(cuò)誤處理需要對(duì)所有可預(yù)見的故障實(shí)例進(jìn)行測(cè)試。
3.接受集成測(cè)試:雖然單元測(cè)試擅長(zhǎng)于評(píng)估代碼的邏輯方面,但它們可能會(huì)無意中忽略與輸入/輸出操作相關(guān)的潛在問題。為了解決這個(gè)問題,集成和執(zhí)行徹底的集成測(cè)試變得必不可少。這些測(cè)試模擬真實(shí)世界的場(chǎng)景,驗(yàn)證代碼在與外部系統(tǒng)或資源交互時(shí)的行為。
協(xié)作與團(tuán)隊(duì)合作
有一條要記住的黃金法則:在遇到難以解決的問題時(shí),可以尋求他人幫助。他人的幫助對(duì)于你自身能力實(shí)現(xiàn)指數(shù)增長(zhǎng)至關(guān)重要。
創(chuàng)建一個(gè)健壯的代碼庫不是一項(xiàng)單獨(dú)的任務(wù),而是需要團(tuán)隊(duì)的努力。盡管開發(fā)人員的職業(yè)表面上看起來是內(nèi)向的,但協(xié)作構(gòu)成了編碼的支柱。這種合作超越了企業(yè)的界限。
GitHub、LeetCode和StackOverflow等平臺(tái)展示了編碼社區(qū)的活躍和社交性質(zhì)。如果你遇到一個(gè)以前沒有遇到的問題,很可能別人也遇到過。因此請(qǐng)始終對(duì)尋求幫助持開放態(tài)度。
代碼文檔的最佳實(shí)踐
無論是公共API還是內(nèi)部代碼,可靠的文檔是游戲規(guī)則的改變者。不過,現(xiàn)在將重點(diǎn)放在了編碼方面。
確保一流的文檔實(shí)踐不僅僅是為了簡(jiǎn)化開發(fā)人員的入職過程,而是為了向開發(fā)團(tuán)隊(duì)提供一個(gè)對(duì)代碼庫有清晰見解的寶庫。這種清晰性推動(dòng)了學(xué)習(xí),使每個(gè)參與者都受益。
預(yù)先編碼和研究
這是啟動(dòng)項(xiàng)目的所有要素的起點(diǎn)——從系統(tǒng)設(shè)計(jì)到指向相關(guān)文檔的關(guān)鍵鏈接。
在預(yù)編碼文檔中,開發(fā)人員不僅可以找到新的表結(jié)構(gòu)和修改,還可以找到指向第三方文檔的有價(jià)值的鏈接。需要記住的是,充分的準(zhǔn)備為成功奠定了基礎(chǔ),將會(huì)事半功倍!
編碼期間
這是開發(fā)人員在代碼中嵌入注釋的地方。這些注釋作為指南,揭示了代碼功能背后的復(fù)雜性和意圖。
事后編碼
這是有效利用代碼的指導(dǎo)中心,也是將來修改代碼的指南。它是引導(dǎo)用戶使用代碼的指南,并概述了后續(xù)修改代碼的路徑。
結(jié)論
優(yōu)秀的代碼遵循可靠的命名約定,保持模塊化,遵循“不要重復(fù)自己”(DRY)原則,確??勺x性,并經(jīng)過徹底的測(cè)試。編寫這樣的代碼需要混合文檔、協(xié)作以及為編碼人員提供幫助。
文章標(biāo)題:Tired of Messy Code? Master the Art of Writing Clean Codebases,作者:Upasana Sahu