自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

為什么說Kotlin的可讀性比Java好?

開發(fā) 后端
不久之前,我看了一篇文章,大意是Kotlin與Java之間的對比,像這種文章,我一般是直接忽略的,但是那天我還是打開了,然后就看到一個非常吃驚的結(jié)果。里面有一段是關(guān)于Kotlin與Java之間可讀性的對比的文章,作者的結(jié)論是:Kotlin并不比Java更具有可讀性,所有認(rèn)為Kotlin比Java更具有可讀性的結(jié)論都是“主觀性”的。

不久之前,我看了一篇文章,大意是 Kotlin 與 Java 之間的對比,像這種文章,我一般是直接忽略的,但是那天我還是打開了,然后就看到一個非常吃驚的結(jié)果。里面有一段是關(guān)于 Kotlin 與 Java 之間可讀性的對比的文章,作者的結(jié)論是:Kotlin 并不比 Java 更具有可讀性,所有認(rèn)為 Kotlin 比 Java 更具有可讀性的結(jié)論都是“主觀性”的。

并且作者舉了一個在我看來,不知道該怎么來描述的例子:

這個作者的大意是,上面這段文章,你多讀個兩三遍,你也會很快的理解它的意思,所以“對于熟練的讀者而言,外觀很少會成為可讀性的障礙。”

我不知道,如果某一天,這個作者突發(fā)奇想,決定全部使用大寫字母來寫代碼——所有的類名、方法名、局部變量成員變量名等等全部使用大寫,我不知道跟作者合作的同事是不是會欣然的耐心的把作者所有的代碼先讀它個兩三遍,然后再來慢慢的理解它的意思。如果是我,我不會。如果在小紅書有個同事非要執(zhí)意這樣寫代碼,理由是“你多讀個兩三遍不就好了嘛?”我想我只能把他開除了。

其實(shí),如果一段代碼需要你多讀個兩三遍才能很好的理解,這本身不就說明,這段代碼的可讀性不高嗎?這里的重點(diǎn)是,這里的這一段大寫的文字你看個三遍,再看的話,是熟悉了,但是再看別的用大寫寫的文字片段,你依然要很費(fèi)勁。所以,這個例子是不能代表大寫這種風(fēng)格的可讀性的。在比較兩種不同的風(fēng)格的可讀性的時候,你不能用具體的某一個一次性的片段來說明。

另外,這篇文章還暗含了這樣一個觀點(diǎn),那就是,代碼的可讀性,僅僅是指,看到一段代碼,能不能理解這段代碼的含義。這是一個很多人都會錯誤的觀點(diǎn)。

但是,在真正工作中,代碼的可讀性,恐怕不至這一個方面。為了考察所謂代碼的可讀性涉及到哪些方面,我們來假設(shè)兩個 case:

你去到一家新公司,接手一個新項目。這個時候,你的需求是,快速了解某個類、某個模塊、某個方法做的是什么事情。在這個基礎(chǔ)上,整個 app、模塊的結(jié)構(gòu)是怎么樣的。

你老板叫你 fix 一個 bug,這個 bug 是另外一個同事寫的,今天這個同事請假了不在。在這個 case 里面,你需要的是,快速的定位到出問題的代碼在什么地方,然后再盡快的了解這個地方的代碼做了什么事情,并且保證你的理解是對的。

所以,總結(jié)一下,代碼的可讀性,可以歸納成三點(diǎn):

  1. 理解一段代碼的速度
  2. 找到你關(guān)心的代碼的速度
  3. 確保正確理解一段代碼的難易程序。這跟***點(diǎn)看似一樣,其實(shí)還真不一樣,下面你會看到。

下面,依次解釋一下這三點(diǎn),以及為什么說,Kotlin 的可讀性會對 Java 高。

1. 理解一段代碼的速度

如果大家仔細(xì)的思考下,你會發(fā)現(xiàn),我們在理解一段代碼的時候,大多數(shù)情況下,我們是想要了解這段代碼做了什么事情,是這段代碼的意圖(Intention),而不是具體這個事情是怎么做的。比如一個 Button 被點(diǎn)擊了,我們的 App 做了什么,是做了什么運(yùn)算,發(fā)了網(wǎng)絡(luò)請求,還是保證了一些數(shù)據(jù)到數(shù)據(jù)庫。也就是說,大多數(shù)情況下,我們關(guān)心的是 What,而不是 How。只有少數(shù)情況下,我們會關(guān)心“How”,一是出于學(xué)習(xí)的目的,我們想要了解一個算法是怎么實(shí)現(xiàn)的,一個效果是怎么實(shí)現(xiàn)的,這個時候,我們會關(guān)心“How”。二是當(dāng)這個“How”出了問題的時候,就是有了 Bug,我們要去了解這個 “How”,然后再 fix 過來。而且,即使是在這些少數(shù)情況下,了解“How”的過程,也只不過是了解一個個子“What”的過程。

敏捷開發(fā)和 TDD 先驅(qū)、JUnit 開發(fā)作者和一系列經(jīng)典編程書籍作者 Kent Beck 提出了一個著名的“four rules of simple design”,是以下 4 條:

  1. Passes the tests
  2. Reveals intention
  3. No duplication
  4. Fewest elements

***條 Passes the test 說的是程序的正確性。第二條 Reveals Intention,說的就是我們這里討論的“What”。

那么,Kotlin 相對于 Java,在幫助我們了解“What”,在幫助 Reveals Intention 這方面,有什么樣的優(yōu)勢呢?我們看一個簡單的例子:

在這段 Java 代碼例子中,這 7 行代碼做的事情很簡單,就是從 personList 中找出 id 值等于 somePersonId 這個變量的值的那個 Person,然后賦值給 person 這個變量。要理解這段代碼并不難(其實(shí)后面你會看到,要確保正確理解這么代碼也沒那么簡單),但是速度并不快,你必須從頭到尾看完這 8 行代碼,就算你說***兩行可以一掃而過,那也必須看完前面 6 行,你才能知道“哦,原來這段代碼做的事情是,從 personList 中找出 id 值等于。。。”

下面,我們來看對應(yīng)的 Kotlin 代碼是怎么樣的:

  1. val person = personList.find { it.id == somePersonId } 

是的,就一行代碼。看完這行代碼,你就知道了它做的是什么事情。因?yàn)樵谶@行代碼中,find 這個單詞就已經(jīng)表達(dá)出了這里做的事情是“找出一些東西”,而大括號里面,就是找出它的條件。也就是說,Kotlin 的寫法直接就幫我們表達(dá)出了“What”。如果平均來說,一個人理解一行 Java 代碼的速度跟理解一行 Kotlin 代碼的速度是一樣的(雖然在我看來,理解一行 Kotlin 代碼會更容易,因?yàn)?Kotlin 里面有很多幫助開發(fā)者減輕負(fù)擔(dān)的語法,接下來會提到這一點(diǎn)),那么在這個例子中,一個人理解 Kotlin 代碼的速度是理解對應(yīng)的 Java 代碼的 5~6 倍。之所以說 5~6,是因?yàn)樵?Java 里面,你還可以寫成 foreach 語法,如果寫成 foreach 語法的話,那么 Java 代碼是 5 行。但是以我的經(jīng)歷,多數(shù)情況下大家還是會習(xí)慣性的寫成 fori,因?yàn)檫@兩者差別并沒那么大,優(yōu)勢也不是那么明顯。

在 Kotlin 里面,Collection 類有一整套像 find 這樣,直接可以 reveal intention 的方法,簡單點(diǎn)的有 filter、count、reduce,map、any、all 等等,高級點(diǎn)的有 mapTo、zip、associate、flatMap、groupBy 等等。絕大多數(shù)情況下,所有需要手動 for 循環(huán)的地方,都有對應(yīng)的更加能“reveal intention”的方法。

當(dāng)然,如果只有一個 collection,就說 Kotlin 在 Reveal Intention 這點(diǎn)上比 Java 更有優(yōu)勢,那是不夠的。Kotlin 有一系列的機(jī)制和便利,能幫助開發(fā)者更好的達(dá)到“Reveal intension”的目。比如 null 的處理,if、when 表達(dá)式(不是語句),比如循環(huán)的處理,比如所有對象都有的 let, apply, run 等方法,比如 data class 以及它的 copy 方法等等等等。此外,通過 Extension Function 這個機(jī)制,Kotlin 對 Java 中絕大多數(shù)的常用類都作了擴(kuò)展。前面提到的各種 Collection 方法,也是使用這種方式來進(jìn)行擴(kuò)展的。此外,就算有一些類沒有你想要的擴(kuò)展,你也可以非常輕松容易的自己寫一個擴(kuò)展方法,來讓你的代碼更加“Reveal Intension”。

相比之下,跟 Kotlin 相比,Java 代碼更像逼我們?nèi)ネㄟ^了解“how”之后,來總結(jié)歸納出它的“what”。在描述一門語言的時候,有一個術(shù)語叫做抽象程度,也就是一門言語表達(dá)“What”、屏蔽“How”的能力。從這點(diǎn)來說,無疑 Kotlin 的抽象程度是比 Java 要高的,就像是 C 語言的抽象程度比匯編要高一樣。實(shí)際上,我還還真有個朋友拿 Java 比作匯編。他是寫 Scala 的,有一天他這么跟我說:

我之前一年多時間都是寫 Scala 的,現(xiàn)在我看到 Java 代碼,就像在看匯編一樣。

基于 Kotlin 的抽象程度更接近 Scala,我想寫一年多 Kotlin 之后,你也會有類似的感覺。OK,***點(diǎn)講到這里。接下來我們來看第二點(diǎn)。

找到你關(guān)心的代碼的速度

當(dāng)談到 Kotlin 的優(yōu)勢時,有一點(diǎn)我相信是公認(rèn)的,那就是 Kotlin 比 Java 更簡潔。而簡潔帶來的好處之一,就是能夠讓人更快的找到他關(guān)心的代碼,而不用在一堆雜七雜八的沒用的代碼里面去翻找自己在乎的代碼。我們還是以一個例子來說明吧,以下兩段代碼。

如果說,要你找出點(diǎn)擊 loginButton 以后,代碼做了什么事情,那以上兩段代碼中,無疑第二段代碼能讓你更快的找到。

上面這個例子還大大地簡化了很多東西,實(shí)際開發(fā)過程中,代碼更加復(fù)雜,Kotlin 的優(yōu)勢也更明顯。

確保正確理解一段代碼的難易程序

這是很多人會忽略的事情。能否理解一段代碼,跟確保正確的理解這段代碼,其實(shí)中間還是有一些差別的。很多代碼看起來很簡單,但是要確保自己正確的理解,其實(shí)還是非常費(fèi)勁的。還是看文章開頭這個例子:

這一段代碼要確保正確的理解,容易嗎?其實(shí)沒那么容易,如果你工作年限多一點(diǎn),你一定碰到過這樣的代碼,在 for 循環(huán)里面,i 是從 1 開始的,而不是從 0 開始的,或者是中間的終止條件是 i < personList.size() - 1,而不是 i < personList.size(),或者***部分不是 i++,而是 i = i + 2,或者 i--。很多人更是碰到過很多 bug,就是因?yàn)闆]有看清楚這里面 i 的起始值、終止條件,或者是步長導(dǎo)致的。我就曾經(jīng)碰到過很多這樣的 bug,也曾經(jīng)因?yàn)闆]有注意這些地方,而導(dǎo)致過很多 bug,***調(diào)了半天,發(fā)現(xiàn)原來是因?yàn)?for 里面是 i=1,而不是 i=0。那時候,就只能在心里默默的大叫一聲:FUCK!

因?yàn)橛羞@些非典型代碼的存在,所以現(xiàn)在,每次看到這樣寫的 for 循環(huán),我心里都會覺得如履薄冰,會特別小心翼翼的看得很仔細(xì),確保 i 的初始值是什么,終止條件是什么,步長是什么。這在無形之中會給人增加特別大的心理負(fù)擔(dān)。然而因?yàn)槭菬o形之中的,是潛意識里面的,所以一般人不會注意到。畢竟,大家都這么寫,而且寫了幾十年了,能有什么問題呢?其實(shí),是有的。這也是為什么 Java5 增加了 Foreach 語法的原因,然而可惜的是,大部分人并不清楚具體為什么要使用 foreach,而且還聲稱 fori 比 foreach 性能更高,這真是令人遺憾。

說回 Kotlin,那為什么說 Kotlin 代碼能讓人更容易正確的理解呢?

讓我們再看一下上面的例子對應(yīng)的 Kotlin 代碼:

  1. val person = personList.find { it.id == somePersonId } 

在這一行代碼中,你根本無需擔(dān)心 i 的初始值、終止條件、步長,因?yàn)檫@里面根本沒有這些東西。所以,一個很大的心理擔(dān)負(fù)消失了。你也不用擔(dān)心這里面有沒有 break,或者你是否忘了寫 break。

這就是 Kotlin 讓代碼更容易理解的地方。同樣的,像這種減輕看代碼的人心理負(fù)擔(dān)的機(jī)制 Kotlin 里面有很多,這里再介紹一個很重要的“小”特性:使用 val 把一個變量定義成不可變的“變量”。我之前一篇文章說過,Kotlin 的 nullsafety 是我最喜歡的特性,如果說第二喜歡的特性是什么,那就是 val 關(guān)鍵字。在團(tuán)隊里面,我也一遍一遍的跟同事強(qiáng)調(diào),能用 val 的地方就不要用 var。因?yàn)樗鼛淼男睦砩系?relief,是巨大的。我們看以下 LinearLayout 里面的代碼。

如果你寫了個自定義 Layout 繼承自 LinearLayout,結(jié)果它表現(xiàn)出來的樣子不符合你的預(yù)期,你可能會去看源碼??吹缴厦孢@段,***你發(fā)現(xiàn),原來是 mBaselineAlignedChildIndex 這個成員變量的值不對。那么,你怎么知道是哪里改變了這個變量的值,導(dǎo)致它被賦給了一個錯誤的值呢?你可能要在這個類里面找出所有會改變這個變量的地方,然后一個一個去 check,哪里會走到,哪里不會走到。更糟糕的是,也許這個值在某個 public 方法里面被改變了,這樣的話,你還要去找出所有調(diào)用這個 public 方法的地方,然后去確定到底是誰,在哪里調(diào)用了這個方法,而這些調(diào)用的地方又是誰調(diào)用的,導(dǎo)致出錯了。這想想就是一件非常頭疼的事情。

但是,如果這個值是 final 的話,這些麻煩就都不存在了。它的值要么是在它定義的地方就確定了,要么是在構(gòu)造方法里面確定的,你只需要檢查兩個地方就可以了,這對于代碼理解,是一件極大的減少工作量的事情。這,就是為什么 Effective Java 里面,建議把所有能用 final 修飾的地方都用 final 修飾的原因。很多人認(rèn)為,使用 final 是為了避免多線程同步的問題。但是,誠實(shí)的說,算了吧,作為安卓開發(fā)的你,上一次碰到多線程同步的原因?qū)е乱粋€變量的值出錯,是什么時候的事了呢?final 的真正優(yōu)點(diǎn),在于讓人在看代碼的時候,不用到處去找可能會改變這個值的地方,也消去“這個值會不會在哪里被改變”的大心理負(fù)擔(dān)。

思考深入的讀者可能會發(fā)現(xiàn),其實(shí)上面的這個例子有點(diǎn)矛盾。因?yàn)槲艺f的是使用 final 來定義變量,但是像上面的 mBaselineAlignedChildIndex 這個成員變量,是不能加 final 的,因?yàn)樗褪且勺兊陌?,它就是需要在某些條件下被重新賦值的啊,這不是矛盾了嗎?

是的,很多時候,我們不能使用 final 來定義一個成員變量。但是,如果你試著給那些可以加上 final 的成員變量加上 final,你會發(fā)現(xiàn),其實(shí)大部分成員變量和幾乎所有局部變量都是可以加上 final 的,但是現(xiàn)實(shí)情況是什么呢?是幾乎所有的成員變量和局部變量,我們都沒有使用 final 來定義。我們寫代碼的默認(rèn)設(shè)置是,先不加 final,如果在哪個地方編譯出錯了——比如寫一個匿名內(nèi)部類,引用了前面的局部變量——迫使我們使用 final 來修飾一個變量的時候,我們才加上。為什么會出現(xiàn)這種情況呢?有兩點(diǎn)原因:

final 的好處并不為大家所知,也不是一眼能看出來的。

使用 final 要寫多寫一個單詞。

當(dāng)一個東西的優(yōu)勢不是很容易被識別(注意,不容易被識別,不代表這個優(yōu)勢不大,或者不重要,這是兩回事),同時又要多付出一些努力的時候,我們寫代碼的默認(rèn)設(shè)置是不加 final,這就非常合情合理了。

那 Kotlin 在這點(diǎn)上,又有什么優(yōu)勢呢?Kotlin 的優(yōu)勢有幾個,先講一個不起眼的優(yōu)勢:使用 val 來定義“變量”。這里之所以給“變量”加上雙引號,是因?yàn)槭褂?val 來定義的“變量”一旦被賦值,是不能被改變的,所以好像稱他們?yōu)?ldquo;變”量不大合適。但我又找不到合適的詞來叫這個東西,所以暫且還是稱作“變量”吧。

不要小看了這個優(yōu)勢。當(dāng)你可以使用 var 或 val 這兩個看起來寫起來都差別不大的方式來定義一個東西的時候,人們自然會想要去了解,這兩者到底有什么區(qū)別?我應(yīng)該使用哪個?這時候,就會有一些討論,有一些標(biāo)準(zhǔn)出來,人們就會認(rèn)識到,不可變性(Immutability)原來有這么大的價值,原來是這么好的一個東西。同時,因?yàn)?val 和 var 寫起來沒有差別,所以人們也會更愿意使用 val 來定義“變量”。

當(dāng)然,要我說,kotlin 這一點(diǎn)做得還不夠。應(yīng)該像 Rust 一樣,讓可變的變量定義起來比不可變的變量定義起來更費(fèi)勁,這才能更加促進(jìn)不可變量這種好的 practice 的發(fā)揚(yáng)光大。

在 StackOverflow 的調(diào)查中(2017,2016),Rust 連續(xù)幾年被評為“程序員最喜愛的語言(Most Loved)”,這不是沒有原因的,它的設(shè)定也不是沒有原因的。除此之外,Kotlin 還使用了一些方式,來讓原本不能定義為 val 的變量,也可以使用 val 來定義,比如 by lazy 和 lateinit,這些機(jī)制綜合起來,即讓 val 寫起來很容易,也擴(kuò)大了 val 的適合范圍。

上面花了很多篇幅來解釋,Kotlin 中 val 的價值。跟 Collection 中的眾多擴(kuò)展方法一樣,這些都是 Kotlin 中,一些讓代碼更容易理解的機(jī)制。像這樣的機(jī)制還有很多,比如說在 Kotlin 中,if、when(Kotlin 中的 switch)都是表達(dá)式(Expression,會返回一些值),而不像在 Java 中,只是語句(Statement,不會有返回值),比如說對 null 的處理,如果你看過多層嵌套的 null 判斷,你就知道那種代碼看起來有多費(fèi)勁了。而使用 Kotlin,結(jié)合 val,在定義的時候把它定義成非 null,你可以明顯的告訴代碼的讀者,也告訴你自己,這個地方是不需要進(jìn)行 null 判斷的。這就大大的減少了 null 判斷的數(shù)量。

由于篇幅的關(guān)系,這些還有剩下的一些機(jī)制,這里就不展開講了。當(dāng)你寫 kotlin 代碼的時候,多思考一下,Kotlin 為什么要這樣設(shè)定,你就會明白,都是有原因的,多數(shù)情況下,都是有優(yōu)勢的。

為什么代碼的可讀性這么重要?

以上從三個方面解釋了什么叫代碼的可讀性,可以看到,無論在哪個方面,Kotlin 都有比 Java 更大的優(yōu)勢。那接下來的一個問題就是,So what?可讀性有這么重要嗎?能吃嗎?值多少錢?

別說,可讀性還真可以吃,而且很值錢!關(guān)于可讀性的重要性,其實(shí)上面分析什么叫可讀性的時候,已經(jīng)提到了,這里歸納一下,只說兩點(diǎn):

  1. 更快的找到你關(guān)心的代碼,更快的理解代碼。要知道,我們現(xiàn)實(shí)開發(fā)過程中,大部分時間是在看代碼,而不是在寫代碼。更快的理解代碼,意味著更高的工作效率,節(jié)省更多的時間,時間就是金錢,所以更高的可讀性,意味著省錢?;蛘哂檬∠聛淼臅r間去賺更多的錢。
  2. 更容易正確的理解代碼,從而不會因?yàn)閷洗a的理解不到位而改錯,造成新的 bug。大家可以回想一下,過去有多少 bug 的發(fā)生,是因?yàn)閷z留代碼的理解不到位,不全面導(dǎo)致的呢?在小紅書,這個比例不少,也造成過不小的問題。痛定思痛,我們現(xiàn)在能做的,就是引以為戒。寫代碼的時候,重視可讀性,讓后來的人,讓后來的自己,不要再吃這樣的虧,不要再背這樣的鍋。

“人生苦短,快用 Kotlin,珍愛生命,遠(yuǎn)離 Java”。這是小紅書安卓客戶端 Coding Style 的***條規(guī)則。順便說一句,使用 Kotlin 之后,目前我們的 app crash 原因里面,Top10 沒有任何一個 NullPointerException 了,Top20 有兩個,這兩個都是發(fā)生在系統(tǒng)層面的,也就是說,我們自己代碼里面的 NullPointerException,基本都已經(jīng)消滅了。而在 6 月份,上 Kotlin 之前,我有統(tǒng)計過的一次,Crash 里面 Top10 有 5 個 NullPointerException,Top20 里面有 12 個。所以 Kotlin 的作用,是非常明顯的。目前,小紅書總體的 Crash 率,除非意外發(fā)生,可以很輕松的保持在 0.2% 甚至 0.1% 之內(nèi),這在業(yè)界,即便不是***的水平,也是個非常好的水平。

在 Google 聲明 Kotlin 成為安卓開發(fā)的官方支持語言那一天,我建了一個微信群。目前偶爾還是有人在群里面問,Kotlin 值得學(xué)習(xí)嗎?有風(fēng)險嗎?看到現(xiàn)在還在問這樣的問題,我是覺得有點(diǎn)遺憾的。希望上面的文章能讓你從代碼可讀性的角度,了解 Kotlin 的優(yōu)勢。讓好的技術(shù)在這個世界盛行,對技術(shù)人來說,是一件非常值得欣慰的事情。 

責(zé)任編輯:龐桂玉 來源: 移動開發(fā)前線
相關(guān)推薦

2021-10-09 10:24:53

Java 代碼可讀性

2017-10-30 15:22:29

代碼可讀性技巧

2021-04-01 16:43:05

代碼可讀性開發(fā)

2015-08-27 13:11:18

JavaScript代碼

2023-10-10 18:12:02

函數(shù)編程語言

2021-01-26 09:18:27

Shell腳本網(wǎng)站

2024-04-23 08:01:20

面向?qū)ο?/a>C 語言代碼

2016-12-14 12:02:01

StormHadoop大數(shù)據(jù)

2017-02-14 14:20:02

StormHadoop

2022-08-29 00:37:53

Python技巧代碼

2021-06-15 09:12:19

TypeScriptTypeScript Javascript

2022-08-23 14:57:43

Python技巧函數(shù)

2019-12-03 09:32:32

JavaScript代碼開發(fā)

2022-11-04 11:18:16

代碼優(yōu)化可讀性

2024-01-31 08:04:43

PygmentsPython

2020-11-08 14:36:27

pandas數(shù)據(jù)分析pipe()

2023-10-30 18:05:55

Python類型

2021-05-12 08:15:53

HTTPSHTTP安全

2017-09-22 11:31:28

KotliJava編程語言

2024-10-11 06:00:00

Python代碼編程
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號