1. 程式人生 > >Welcome to the World

Welcome to the World

-思緒來源:我在初學演算法的時候,一直不願意呼叫除了基礎函式以外的其他功能函式,特別是有些函式裡面就只有一句程式碼,這...有什麼封裝的必要?(例子:資料結構順序表,其新增刪除肯定屬於基本邏輯操作,但這順序表的長度到底有什麼封裝的必要,順序表誒,四捨五入等於陣列,求個長度還封裝,哇,一句程式碼的事情誒,有沒有搞錯(我就舉一個小例子

-我願意改變,但有誰能告訴我原因是什麼好嗎,跪求啊

-真的是感悟,都快上升到哲學問題了....寫的就是一種感覺

-意識流

我有認真思考過“應該如何去程式設計”,但是在學習的過程中我卻只能讀到一系列指示,像這樣命名變數(大小駝峰法、匈牙利命名法、下劃線法)、像這樣組織程式碼(結構化,流程化)、像這樣封裝函式(高內聚低耦合)等等等等。

是,在大部分情形下,這些都是不錯的做法。但這樣做的理由是什麼?

大家寫程式碼都有自己的習慣偏好,對吧。雖然程式碼個人風格太嚴重不一定是好事,因為別人可能看不懂這份程式碼,由此可見這份程式碼的可讀性和溝通性不見得好,這會帶來很多麻煩事。但是,這不代表我會在一無所知毫無辨別能力的情況下,就隨隨便便隨波逐流地採用大眾方法。

我知道很多事情不能一概而論,不同的情況下有著不同的最優解法,但這些做法到底在什麼樣的上下程式碼中才能成為“通法”卻沒人指出。就像最基本的變數命名,如果要準確表達出變數的職責而使用很長的名字,這會使程式碼變得冗長從而加大閱讀難度,可是如果為了圖方便而使用縮寫命名,那效果會更加不好,因為這太容易產生歧義了,其名字不具有描述唯一性。那面對這種情況我們該如何抉擇呢?

其實,我在課本中從來沒有看見過這些答案。

回想當初,我也有這方面的壞習慣,在最初學習程式設計的一段不算太長的時間裡,我每天樂滋滋地跟著課本去使用一套abcdefjhijklmnopqrstuvwxyz的命名覺得自此打遍天下無敵手......

後來發現不對勁兒了,因為在複習的時候我有點看不懂我以前寫的程式碼了,這感覺可不好受,看個程式碼跟做閱讀理解題一樣,說出去搞笑呢。於是我開始注意自己的程式碼書寫規範,有去搜索過一些文章部落格,也有瀏覽過一些公司釋出的程式碼規範手冊(如谷歌、BAT、華為),建議新手小白們有空也去看看學習學習。

但是看過以後呢,我覺得有些規範是說得很對,但為什麼很對就不知道了。呵,學不到本質,這不是我想要的。不過不知道也沒關係,畢竟你才剛開始學習能意識到程式碼書寫規範的重要性和保有良好的註釋習慣這就已經是個很好的開始了,但顯然我還是想知道原因。

我有思索過、有考慮過、也有糾結過,但有時候我得出的結論明顯並沒有我想象期望中的那般好。可那又怎樣,新手本來就很容易犯各種各樣的錯誤,如果你現在不花時間去思考,等到以後還不是會花時間思考,一樣的沒差。

只是令我感到咋舌的是,如果每次學習自己喜歡的新事物新規則時就直接拿來用了,那你可能會與這些規則背後所蘊含的斟酌與考量其產生的奧妙感覺擦肩而過。如果每次寫程式碼都按著模版照搬,管它三七二十一的反正現成的程式碼能用就是了,那這種複製貼上簡直和程式碼界的搬運工沒有任何區別。其實,此處涉及到造輪子和用輪子之間的權衡問題,關於這個的討論網上有很多,不妨搜搜看。

嗯,寫這麼些話是希望大家能抓住每一個有感而發的思考瞬間,不要錯過與那些隱含知識碰面的機會。但值得注意的是,千萬不要刻意地去思考一件事,那是挑刺兒。不過,若實在思考不出來也沒關係,因為你與程式碼打交道的時間還很長,可以不急於一時。

畢竟在後續成長實踐的過程中,你會逐漸有深刻認識的,就像學習軟體開發一樣,能慢慢地體會到軟體不是簡單的編寫程式,其包含了很多技術問題間的協調,如分析方法、設計方法、形式說明方法、版本標準等等。到那時候你會自然而然地為了解決問題而去思考的,所以別擔心,有機會的。

但對於我來說的話,思考這種情況還是很常見呢。我想著知其然還要知其所以然,就這樣深究下去了,沒錯,這遲早會上升到哲學問題——“如何程式設計?”

可是這種哲學問題真的很容易泛泛而談,即使大家給出了許多規則,但看到規則的大多數菜鳥們(包括我),其實根本不知道規則背後隱藏著什麼樣的思索,也不知道什麼時候該打破規則、突破束縛。嘶,這就很扎心了。

我當然意識到了程式碼可讀性的寶貴,也知道要寫出高質量的程式碼勢必要付出很多心血,融合自己與他人的程式碼風格總是有許多深思熟慮的。畢竟優秀的程式碼,絕非僅僅是功能的堆砌,它需要做到能有效的傳達資訊。因為有無數個事實表明,修改程式碼是編寫程式碼成本的3倍、5倍之多,而且別人閱讀理解你程式碼的時間可能要遠遠超出你編碼所用的時間。

所以,要學會利用程式碼去精細準確的傳達出你的想法,學會用程式碼與他人溝通,絕對不要寫出垃圾程式碼。要知道,寫出簡潔明瞭結構良好且效率高的程式碼是一件重要、困難並且有趣的事,它值得每一個人下功夫去研究。

但這種程式碼不會自然產生,它是由數十個數百個看似瑣碎,實則無比重要的決定堆砌出來的。我們在創造高質量程式碼的過程中,不斷地在做出一些微小卻重要的決定,雖然每一種程式設計方式都由許多決策組成,而且它們之間互相支援協同工作,但從中抽出一條卻不一定能有效果。比如,片面地追求最精簡最優化或最高效的程式碼就是個誤區,程式碼並不是行數越少就越好,如果仔細注意或觀察就會發現我們使用最多的程式碼是那些邏輯清晰結構良好的程式碼,而非那些標新立異看起來高深莫測的程式碼,當然炫耀的除外。

歸根結底,這麼說這麼做的目的只有一個,就是希望你的程式碼可以清晰的表達出你的意圖,並且自始至終保持一致性,讓其他人可以理解並信任你的程式碼,能夠信心十足的修改它,這樣的程式碼才是好程式碼。

其實,程式碼要寫得好,要點之一就是不能repeat東西。這是因為一旦要改需求,你會欲哭無淚的,程式設計師還是要減少修改程式碼的痛苦。那麼重點就來了,在學習資料結構的時候關於函式封裝的問題我可是吃過大虧,呵,接下來我將講述我的慘痛經歷。

不過在此之前,明確指出函式封裝是有必要的,先不論你的程式碼風格或偏好習慣如何。

嗯...我印象最深的是當時用順序表完成字串操作的那個程式碼,雖然現在回看過去覺得很簡單,但是那個時候的我確實不明白為什麼要把一些很基本的語句包裝起來。我一直覺得把求length的方法專門單獨寫進一個函式裡面是如此的畫蛇添足和多此一舉,所以我理所當然的沒聽老師的方法也沒按照課本上提供的案例那樣來做,偏要自己寫。

但是你知道的,關於陣列下標的計算一直都是一個很容易出錯的東西,所以我在後續手動修改順序表長度時遇到了很多挫折。首先,肉眼查錯和手動列舉修改程式碼是非常耗時耗力的傻行為,這不僅過程痛苦而且還容易出現紕漏,最重要的是這種修改過程會影響你理清思路,因為你的注意力全部集中在修改語句上,而非修改結構組織上。於是稍有不慎,改著改著就發現程式碼結構亂了,這太糟糕了。

我呢,在修改完程式碼後再次進行測試時,發現有個功能的結果出錯了,但找了半天沒覺得哪裡出錯。畢竟思路什麼的都是對的,而且我還自以為挺謹慎的,不可能出錯啊。直到我決定開始逐行排查程式碼的邏輯,最後發現有一個函式的length++初始值不對,多加了個1。我的天吶,我當時簡直都要被氣笑了,錯在這個地方讓我找了近一個小時。

那個時候我就明白了,像求長度這種使用率較高的底層程式碼,如果你把它封裝成了函式,那麼在你需要修改它的時候就只用在函式裡面修改一次即可,不用像我之前那樣手動一個個的去修改,找不全還很容易出錯。所以這種封裝的感覺啊,還挺像“一鍵修改”功能的,如同機器上的零部件一樣製造一次後批量使用。

是的,函式封裝相當於替換或減少了重複性的程式碼,可以使程式精簡化。試想一下,當所有程式碼都寫進一個main函式裡面是不是很可怕,畢竟,一個函式實現多個功能會給開發、使用、維護帶來很大的困難。若將沒有關聯或者關聯很弱的語句放在同一函式中,會導致函式職責不明確,讓人難以理解、難以測試、難以改動,所以將重複性的程式碼提煉成函式可以降低維護的成本。

但事情總有兩面性,如果掌握不到封裝的精髓,那很容易就犯下封裝過度的錯誤。其具體表現就是,不斷的分解再分解問題卻對於精簡邏輯結構毫無頭緒,導致寫了無數行程式碼封裝了一堆函式但就是看不到一個完整的功能,分裂問題起來收不住,不知道何時適可而止。往往一個函式裡面就三兩行程式碼,互相之間呼叫這個呼叫那個的,引用得亂七八糟,最終把功能實現了但程式碼的維護性也讓人不忍直視,自以為封裝細緻,其實是拿捏不好這個度。

所以你就知道了,我為什麼當時會對那個length長度的封裝抱有這麼大的抵觸,因為封裝的函式裡面就只有一行程式碼,這真的讓人很難以接受啊。

那麼,在程式設計時如何決定是否將程式碼封裝成函式,或者如何辨別才不會做無所謂的封裝呢。我覺得還是要以程式碼邏輯直觀為最優先原則,然後講究封裝的高內聚低耦合,剩下的其他原則均低於此,即使是程式碼的時間和空間複雜度也不行。

當然,還有幾個簡單易懂的封裝指標,可以借鑑一下:

1、函式要短小,不超過50行,從而避免使程式碼閱讀者一次記住太多上下文。

2、函式只做一件事,一個方法只實現一個功能,要減少模組間的互相干擾,讓各個模組的修改變化儘可能小的影響其他模組。

3、函式要使用“當且僅當一次”原則,不能重複,即使整合兩個類似的函式是相當困難的。除此之外,若程式碼只被呼叫過一次且在邏輯上沒有獨立存在的必要,那這樣的程式碼就不應該被封裝成函式。

4、函式巢狀不能超過四層,尤其是if、for、while等語句之間互相包含的深度,巢狀多個語句或函式會影響執行效率,就像開車走彎路一樣,而且每級巢狀都會增加閱讀量,使你的腦力在不斷消耗直至宕機。這就像當你寫了無數的if-else之後發現了switch-case時的心情,或者第一次聽到函式指標這個事物時的心情。

5、函式內邏輯要最直觀、最好理解,整體而言簡潔可用即可。這不是程式碼高不高階的問題,高質量的程式碼是簡單直白的,能用乾淨利落的抽象和直截了當的控制語句將函式有機組織起來,並且能清晰的傳達出設計者的意圖,這才是最重要的。

畢竟,封裝的本質就是要減少重複資訊的產生,歸根結底無非就是為了能更好的重構程式碼,但這只是一部分原因,其實重複的本質並不僅僅侷限於此。

重複,是由於你把同一個資訊散播在了各個地方,這相當於複製了程式碼。可是就算你把重複的程式碼抽象成了函式,但函式呼叫了幾次,這實際上也等於重複了幾次。那麼這時就應該從程式碼的結構框架上改,也就是利用資料結構和演算法的知識來解決重複問題。

其實,“去掉重複的資訊”這個問題也很值得深思,因為就算我們撇開程式碼的精簡度不談,就從你以後的工作來說,由於客戶的需求總是在變,如果每次你遇到類似問題時,經常就直接寫,而不是找曾經寫過的函式來擴充套件,那這樣解決問題的效率就高不起來。所以為什麼不把你需要的各種資訊提取出來,把專用函式擴充套件變成通用函式來減輕自己的負擔。

所以記住吧,如果資訊一旦被重複,那你的程式碼就會出現不同程度的腐爛和破窗,不要讓你的這份程式碼散發出不好的氣息。

啊,當然了,凡事不能一概而論,這並不是什麼程式碼都能重構的,就算你想重構但有些程式碼就是不能重構也沒法重構。

因為在一個具有一定規模的系統中,存在複雜模組是無法避免的事情,一次性把這些複雜模組都標註出來並不現實,而且我們對這些模組進行調整和優化也存在風險。所以,以合適的策略去優化真正需要優化的部分是很重要的,不要侷限於重構。

況且還有些東西根本就沒法重構,比如幻數。就像雜湊表中的133,或者你自己在編寫程式碼時寫的0x2123、0.0211f等東西,當時你是明白這個數字的意思,但是別的程式設計師看這個程式碼可能很難理解,甚至過了一段時間之後,連你自己再看這段程式碼也忘記了這個數字代表的含義。

總之,你不記得也不知道這個數字的具體含義究竟是代表著什麼,或者這個數字根本就沒有什麼含義,但是程式碼編譯後程序依然可以正常執行,那這種東西就是幻數。舉一反三,就算有一段很莫名其妙的程式碼擺在你面前,你不懂這段程式碼有什麼意義,但它就是能很好的執行程式,而且沒了它還不行。那這種東西怎麼重構,它根本就不好重構,所以就別重構了。

對於這種現象呢,很多人覺得這種程式碼是定時炸彈,不改掉它心裡不痛快。可是,只有當我們真的需要關注並修改它時,它才是問題所在,如果沒有人需要閱讀或修改這段程式碼,那麼它的複雜度就算高成指數倍,那又有什麼關係呢?但如果你實在犯了完美主義的強迫症,那就去改吧,畢竟心裡痛快比較重要。

所以你會發現,大家都說封裝好,但有些時候封裝程式碼就不好,大家說重構很重要,但有時候重構根本就沒有意義。在一些特定的情況下,規則並不適用,那麼當你學著瞭解規則的同時,也得想一想規則背後的原因,不然這跟封建迷信有什麼區別。

最後,祝大家都能寫出好程式碼,嗯。