Java使用connectTo方法提高代碼可續(xù)性詳解
源代碼有兩種不同的用戶:程序員和計算機。一方面,計算機既能處理干凈、結(jié)構(gòu)良好的代碼,也能處理混亂的代碼。另一方面,程序員對代碼的可讀性很敏感。甚至是代碼中的空白、正確使用縮進(這與計算機完全無關(guān))也決定了代碼容易理解或難以理解。
此外,代碼的可讀性也提高了可靠性,因為通常不容易隱藏一些bug。并且提高了可維護性,因為它更容易修改。
關(guān)于可讀性的一些想法
編寫可讀的代碼是一門被低估的技術(shù),學(xué)校很少教授這種技術(shù),但它卻與軟件的可靠性、維護和發(fā)展密切相關(guān)。程序員通常學(xué)習(xí)用機器容易理解的東西來實現(xiàn)所需功能的代碼。這個編碼過程需要添加一層又一層的抽象來將功能分解為更小的單元。
Java語言中,這些抽象是包、類和方法。如果整個系統(tǒng)足夠大,就沒有程序員可以單獨控制整個代碼庫。有些開發(fā)者對某個特定的業(yè)務(wù)有一個縱深的認(rèn)識。其他開發(fā)人員可能只負(fù)責(zé)一個抽象層并維護它的API。他們都需要經(jīng)常閱讀和理解別人編寫的代碼。提高可讀性意味著將程序員理解一段代碼所需的時間最小化。
如何編寫可讀的程序?用一句關(guān)于表現(xiàn)力的格言來總結(jié)就是,簡單而直接地說出你的意思。事實上,可讀性意味著清楚地表達代碼意圖。統(tǒng)一建模語言(UML)設(shè)計師之一格雷迪·布克(Grady Booch)給出了一個自然的類比:干凈的代碼讀起來就像優(yōu)美的散文。
寫好散文不是簡單地遵循一套固定的規(guī)則,而是需要練習(xí)和閱讀著名作家的偉大文章,這一過程可能需要數(shù)年時間。幸運的是,與自然語言相比,計算機代碼的表達能力非常有限,所以編寫出干凈的代碼比寫優(yōu)美的散文更容易,或者至少更有條理。業(yè)界對重構(gòu)和編寫干凈的代碼越來越感興趣??勺x性已經(jīng)成為敏捷開發(fā)中最重要的關(guān)注點之一。
試圖使用一組簡單的數(shù)字指標(biāo)(如標(biāo)記)來評估可讀性。標(biāo)識符的長度、表達式中出現(xiàn)的括號的數(shù)量,等等。這項工作仍在進行中,要達成一個穩(wěn)定的共識還有很長的路要走,我們接下來將通過一個例子來說明和解釋代碼可讀性的一些改進點。
整理connectTo方法
現(xiàn)在我們將注意力轉(zhuǎn)向一個名叫connectTo的方法,該方法會將兩個組里面的容器進行合并,并且容器里面的水會被均分。對其進行重構(gòu)以提高可讀性。首先查看初始版本的實現(xiàn):
public void connectTo(Container other) {
// 如果兩個容器已經(jīng)連接,則不做任何事情
if (group==other.group) return;
int size1 = group.size(),
size2 = other.group.size();
double tot1 = amount * size1,
tot2 = other.amount * size2,
newAmount = (tot1 + tot2) / (size1 + size2);
// 合并兩個組
group.addAll(other.group);
// 更新要連接的所有容器的組
for (Container c: other.group) { c.group = group; }
// 更新所有新連接的容器
for (Container c: group) { c.amount = newAmount; }
}
這里有一個缺陷:它包含了大量的注釋,試圖解釋每一行代碼的含義。有些程序員關(guān)心他們的同事,想要他們更好的理解代碼,自然會添加這樣的注釋。然而,這并不是實現(xiàn)容易理解這一目標(biāo)的最有效的方法。更好的選擇是使用提取方法的方式來進行重構(gòu)。
可讀性提示:“提取方法”重構(gòu)規(guī)則——提取可以實現(xiàn)某一個小功能的代碼塊轉(zhuǎn)到一個新方法并使用描述性名稱。
我們可以在connectTo方法中應(yīng)用這種技術(shù)。事實上,我們可以拆分5個新的方法,以及獲得一個新的、可讀性更強的connectTo方法:
/** Connects this container with another.
*
* @param other The container that will be connected to this one
*/
public void connectTo(Container other) {
if (this.isConnectedTo(other)) return;
double newAmount = (groupAmount() + other.groupAmount()) /
(groupSize() + other.groupSize());
mergeGroupWith(other.group);
setAllAmountsTo(newAmount);
}
這個方法更短,可讀性更強。如果你試著把這個方法大聲讀出來,你會發(fā)現(xiàn)它幾乎可以變成可以被理解的一個短文。為此,我們引入了五種適當(dāng)?shù)闹С址椒āJ聦嵣?很多業(yè)內(nèi)的大佬都認(rèn)為長方法是一種不好的代碼味道,提取方法來消除這種壞味道是普遍被采納的一種重構(gòu)技術(shù)。
添加注釋只能解釋部分代碼,而提取方法既解釋代碼又隱藏生成過程代碼——將代碼提取到單個方法中。在這個例子中,它會使原來的方法抽象級別保持在更高、更統(tǒng)一的高度,避免了舊版本代碼中的高層API解釋和底層實現(xiàn)錯綜復(fù)雜地交織在一起。
用查詢替換局部變量是另一種可用于connectTo方法的重構(gòu)技術(shù)。
可讀性提示:“用查詢替換局部變量”重構(gòu)規(guī)則——更改局部變量,通過調(diào)用一個計算其值的新方法來替換該量。你可以將此技術(shù)應(yīng)用于局部變量newAmount,該變量只分配一次,然后用作setAllAmountsTo方法的參數(shù)。應(yīng)用該技術(shù)可以直接刪除變量newAmount,并將connectTo方法的最后兩行替換為以下內(nèi)容。
mergeGroupWith(other.group); setAllAmountsTo(amountAfterMerge(other));
amountAfterMerge是一個計算合并后的每個容器水量的新方法。但是,稍加思考就會發(fā)現(xiàn),amountAfterMerge方法需要克服很多困難才能完成任務(wù),因為在調(diào)用方法時,兩個group已經(jīng)完成了合并。group已經(jīng)包含了other的group。一個很好的折衷方案是將計算新水量的表達式封裝到一個新方法中,同時保留局部變量,以便在合并組之前計算出新的量。
final double newAmount = amountAfterMerge(other); mergeGroupWith(other.group); setAllAmountsTo(newAmount);
總而言之,我不建議進行這種重構(gòu),如抽出5個方法版本中的代碼所示newAmount表達式是可讀的,不需要隱藏在單獨的方法中。當(dāng)它替換的表達式很復(fù)雜或在類中多次出現(xiàn)時,“用查詢替換局部變量”規(guī)則通常更有用。
現(xiàn)在看看可讀版本中connectTo方法的五個新支持方法。在這五個方法中,有兩個最好聲明為私有的,因為它們可能導(dǎo)致容器對象處于不一致的狀態(tài),不應(yīng)該從類外部調(diào)用。他們是mergeGroupWith方法和setAllAmountsTo方法。
mergeGroupWith方法合并兩組容器而不更新它們的水量。如果有人單獨從外部調(diào)用它,很可能使一些或所有容器的水量發(fā)生錯誤。這個方法只有在使用它的上下文中才有意義:在connectTo方法的末尾,然后調(diào)用setAllAmountsTo方法。事實上,它是否真的應(yīng)該獨立成一個方法是有爭議的。
一方面,讓它獨立可以通過給予它一個好名字來解釋它的用途,而不是像開始的版本那樣使用注釋解釋。另一方面,獨立出來的方法可能在錯誤的上下文中被調(diào)用。因為我們是為了可讀性而優(yōu)化的,所以創(chuàng)建獨立的方法會更好一點。類似的權(quán)衡setAllAmountsTo方法也適用。
private void mergeGroupWith(Set<Container> otherGroup) {
group.addAll(otherGroup);
for (Container x: otherGroup) {
x.group = group;
}
}
private void setAllAmountsTo(double amount) {
for (Container x: group) {
x.amount = amount;
}
}私有方法不值得用Javadoc注釋。它們只在類內(nèi)部使用,所以很少有人覺得有必要了解他們的細(xì)節(jié)。因此,添加注釋不是太有必要的。注釋的成本并不限于編寫它們所需的時間。就像其他源代碼一樣,它需要維護,否則可能會過時。也就是說,隨著版本的迭代,注釋和它所描述的代碼不同步了。
記住:過時的評論比沒有評論更糟糕! 用描述性名稱代替注釋并不能避免這種風(fēng)險。如果編寫的代碼功能和名稱不符了,然后最終仍然可能產(chǎn)生一些過時的名稱,這和過時的注釋同樣糟糕。
其他三種新的支持方法都是只讀特性,不會帶來任何不良影響。我們不應(yīng)該輕易做出讓他們公有化的決定。添加到類中任何公共成員的后續(xù)維護成本都要比添加相同的私有成員的成本大得多。公共方法的額外成本包括:
- 描述其功能的適當(dāng)注釋;
- 條件檢查,以處理可能不正確的輸入內(nèi)容;
- 一套完整的測試,以確保其正確性。
connectTo方法的三個新的公有支持方法:
/** Checks whether this container is connected to another one.
*
* @param other the container whose connection with this will be
checked
* @return <code>true</code> if this container is connected
* to <code>other</code>
*/
public boolean isConnectedTo(Container other) {
return group == other.group;
}
/** Returns the number of containers in the group of this
container.
*
* @return the size of the group
*/
public int groupSize() {
return group.size();
}
/** Returns the total amount of water in the group of this
container.
*
* @return the amount of water in the group
*/
public double groupAmount() {
return amount * group.size();
}順便說一下,isConnectedTo方法還改進了類的可測試性,因為它使以前在實現(xiàn)中需要推測的內(nèi)容都變成了直接可測試的。實現(xiàn)connectTo的六個方法都非常短,其中connectTo是最長的方法本身只有6行。簡潔是干凈代碼的主要原則之一。
到此這篇關(guān)于Java使用connectTo方法提高代碼可續(xù)性詳解的文章就介紹到這了,更多相關(guān)Java connectTo內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
IDEA2019.2.2配置Maven3.6.2打開出現(xiàn)Unable to import Maven project
這篇文章主要介紹了IDEA2019.2.2配置Maven3.6.2打開出現(xiàn)Unable to import Maven project,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
java中for循環(huán)執(zhí)行的順序圖文詳析
關(guān)于java的for循環(huán)想必大家非常熟悉,它是java常用的語句之一,這篇文章主要給大家介紹了關(guān)于java中for循環(huán)執(zhí)行順序的相關(guān)資料,需要的朋友可以參考下2021-06-06

