程序員成長之路(轉)

什么時候才能成為一個專業程序員呢?三年還是五年工作經驗?其實不用的,你馬上就可以了,我沒有騙你,因為專業程序員與業余程序員的區別主要在于一種態度,如果缺乏這種態度,擁有十年工作經驗也還是業余的。

什么態度?專業態度!也就是星爺常說的專業精神。專業態度有多種表現形式,以后我們會一一介紹的。這里先介紹一下有關形象的態度,專業的程序員是很注重自己的形象的,當然程序員的形象不是表現在衣著和言談上,而是表現在代碼風格上,代碼就是程序員的社交工具,代碼風格可是攸關形象的大事。

有人說過,傻瓜都可以寫出機器能讀懂的代碼,但只有專業程序員才能寫出人能讀懂的代碼。作為專業程序員,每當寫下一行代碼時,要記得程序首先是給人讀的,其次才是給機器讀的。你要從一個業余程序員轉向專業程序員,就要先從代碼風格開始,并從此養成一種嚴謹的工作態度,生活上的不拘小節可不能帶到編程中來。

代碼風格有很多種,Windows 和Linux都有自己主流的代碼風格,每個團隊、每個公司也可能有自己的代碼風格,爭論哪種風格好哪種風格壞根本沒有什么意義。有助于其他程序員理解的代碼風格都是可以接受的,因為遵循特定代碼風格的目的就是為了便于交流。

1 命名要展示對象的功能

1.1 文件名

文件名一定要能傳達文件的內容信息,別人一看到文件名就能知道文件中放的是什么內容。把一個類的代碼或者某一類代碼放在一起是好的習慣,這樣就很容易給文件取一個直觀的名字。業余愛好者常常把很多沒關系的代碼糅到一個文件中,結果造成代碼雜亂無章,也很難給它取一個恰當的名字.

1.2 函數名

單詞小寫,多個單詞用下劃線分隔。如:find_node

一個函數只完成單一功能。不要用代碼的長度來衡量是否要把一段代碼獨立成一個函數。即使只有幾行代碼,只要這些代碼完成的是一項獨立的功能,都應該將其寫為一個單獨的函數,而函數名要能夠直觀地反應出它的功能。如果在給函數起名時遇到了困難,通常是函數設計不合理,則應該仔細思考一下并對函數進行相應修改。

1.3 結構/枚舉/聯合名

首字母大寫,多個單詞連寫。如:struct _DListNode

宏名:單詞大寫,多個單詞下劃線分隔。

如:#define MAX_PATH 260

變量名:單詞小寫,多個單詞下劃線分隔。

如:DListNode* node = NULL;

1.4 面向對象命名方式

(1) 以對象為中心,采用“主語(對象)+謂語(動作)”的形式來命名,取代傳統的“謂語(動作)+賓語(目標)”的形式。

如:dlist_append

(2) 第一個參數為對象,并用thiz命名。

如:dlist_append(DList* thiz, void* value);

(3) 對象有自己的生命周期,因此都有相應的創建和銷毀函數。

2 排版布局要美觀大方

2.1 合理使用空行

函數體之間用空行分隔。

結構/聯合/枚舉聲明用空行分隔。

不同功能的代碼塊之間用空行分隔。

將功能類似的代碼(如宏定義、類型定義、函數聲明和全局變量)放在一起,和其他部分用空行分隔。

使用空行時,一行就夠了,不要使用連續多個空行,那樣會讓人感覺代碼段空蕩蕩的。

2.2 合理使用空格

等號兩邊用空格。如:int a = 100;

參數之間用空格。如:test(int a, int b, int c)

語句末的分號與前面內容不要加空格。如:test(a, b, c);

其他能讓代碼更美觀的地方。

2.3 合理使用括號

用括號分隔子表達式,不要只靠默認優先級來判斷。((a && b) || (c && d))

用括號分隔if/while/for等語句的代碼塊,那怕代碼只有一行。

2.4 合理縮進

每一級都正常縮進,用tab縮進取代空格縮進(Linux內核源代碼也遵循此規則)。用空格縮進的目的是防止代碼因編輯器的tab寬度不同而變亂,這個擔心現在是多余的 了,代碼編輯器都支持tab寬度設置了。如果代碼縮進的層次太多(比如超過三層),則可能是代碼設計上出了問題。

2.5 遵從團隊的習慣

這一點是最重要的,一個團隊就要有一個團隊的樣子,不管你的水平有多高,遵循團隊的規則是一個程序員的基本素養。如果團隊的規則確實不好,大家應該一起完善它。做到這一點,你已經離成為專業程序員這個目標更近一步了,重新做一遍練習吧。隨著后面的學習,你就可以真正走進專業程序員這個行列了。

3 誰動了你的隱私

3.1 什么是封裝

人有隱私,程序也有隱私。有隱私不是什么壞事,問題是不應該讓別人知道自己的隱私,否則可能會對自己造成不小的傷害,甚至會連累相關人物跟著倒霉。程序隱私的暴露,造成的不良影響不一定會泄露個人隱私那么大,但也不容小覷。封裝就是要保護好程序的隱私,不該讓調用者知道的事,就堅決不要暴露出來。

3.2 為什么要封裝

總的來說,封裝主要有以下兩大好處。

隔離變化。程序的隱私通常是程序最容易變化的部分,比如內部數據結構、內部使用的函數和全局變量等,我們需要把這些代碼封裝起來,從而讓它們的變化不會影響系統的其他部分。

降低復雜度。接口最小化是軟件設計的基本原則之一,最小化的接口容易被理解和使用。封裝內部實現細節,只暴露最小的接口,會讓系統變得簡單明了,在一定程度上降低了系統的復雜度。

3.3 如何封裝

總的來說,封裝主要有以下兩大好處(具體影響后面再說)。隔離變化。程序的隱私通常是程序最容易變化的部分,比如內部數據結構、內部使用的函數和全局變量等,我們需要把這些代碼封裝起來,從而讓它們的變化不會影響系統的其他部分。降低復雜度。接口最小化是軟件設計的基本原則之一,最小化的接口容易被理解和使用。封裝內部實現細節,只暴露最小的接口,會讓系統變得簡單明了,在一定程度上降低了系統的復雜度。封裝過程中應注意一下問題:

內部函數通常實現一些特定的算法(如果具有通用性,應該放到一個公共函數庫里),對調用者沒有多大用處,但它的暴露會干擾調用者的思路,讓系統看起來比實際的復雜。函數名也會污染全局名字空間,造成重名問題。它還會誘導調用者繞過正規接口走捷徑,造成不必要的耦合。隱藏內部函數的做法很簡單。

(1)在頭文件中,只放最少的接口函數的聲明。

(2)在C文件中,所有內部函數都加上static關鍵字。

全局變量始終都會占用內存空間,共享庫的全局變量是按頁分配的,哪怕只有一個字節的全局變量也占用一個頁,這樣一來就會造成不必要內存空間浪費。全局變量也會給程序并發造成困難,想把程序從單線程改為多線程將會遇到麻煩。重要的是,如果調用者直接訪問這些全局變量,會造成調用者和實現者之間的耦合。

4 Write once, run anywhere(WORA)

4.1 專用鏈表和通用鏈表各自的特點與適用范圍

專用鏈表在這里是指該鏈表的實現和調用耦合在一起,只能被一個調用者使用,而不能單獨在其他地方被重用。通用鏈表則相反,它具有通用性,可以在多處被重復使用。盡管通用鏈表相對專用鏈表來說有很多優越之處,不過草率地斷定通用鏈表比專用鏈表好也是不公正的,因為它們都有自己的優點和適用范圍。()

注意 在本節中,為了避免讀起來拗口,我把雙向鏈表簡寫成鏈表了,希望大家不要介意。

專用鏈表的優點

考慮到鏈表是最常用的數據結構之一,很多地方都會用到它,實現通用的鏈表會更有價值。接下來我們要實現一個通用的鏈表,不過請大家記住,實現通用的鏈表并不是我們的目標,而是我們學習軟件設計方法的手段。前面我許諾過要以簡單的數據結構講述復雜的軟件設計方法,鏈表就是其中的載體之一。

5 擁抱變化

在專用雙向鏈表中,dlist_printf的實現非常簡單,如果里面存放的是整數,用 %d 打印,存放的是字符串,用 %s 打印。現在的麻煩在于雙向鏈表是通用的,我們無法預知其中存在的數據類型,也就是說我們要面對數據類型的變化。怎么辦呢?初學者可以參考的常用方法有以下幾種。

5.1 實現多個函數,需要哪個就用哪個

比如實現dlist_print_int用來打印存放整數的雙向鏈表,dlist_print_string用來打印存放字符串的雙向鏈表等,其他類型都有自己的打印函數。

不過這種做法也有一些缺點。一是每個函數的實現方式類似,會帶來大量重復的代碼。二是由于數據類型的種類不確定,如果為每種數據類型都實現一個print函數,當要存放新的數據類型時,就不得不修改dlist的實現。

5.2 傳入一個附加參數來決定如何打印

比如傳入1表示按整數方式打印,傳入2表示按字符串方式打印,以此類推。

這種做法比第一種好一點,至少不會造成大量重復的代碼。但是同樣存在增加新類型時要修改dlist_print函數的問題。

5.3 調用dlist的接口函數獲取每一個位置的數據并打印出來

這種方法沒有前面兩種方法的缺點,而且是一種相當直觀的方式。但奇怪的是偏偏很少有人使用這個方法,原因可能有兩個:其一是太拘泥于傳統的實現方式而沒有想到這一種;其二是擔心性能問題,因為通過索引取值,每一次都要從頭開始定位,其性能開銷為O.

其實這種方法是可以接受的,dlist_print函數只是用于輔助測試,我們并不需要太在乎它的性能開銷,而且我們很少會在鏈表中存放成千上萬的數據,因此這個函數帶來的性能影響根本沒有想的那樣嚴重。所以在這里我們要介紹一種新的方法。

dlist_print的大體框架如下。

在上面代碼中,我們主要是不知道如何實現 print(iter->data); 這行代碼。那么誰知道呢?很明顯,調用者知道,因為調用者知道鏈表里面所存放的數據類型。好吧,那就讓調用者來做好了,調用者在調用dlist_print時會提供一個函數給dlist_print來調用,這種回調調用者所提供函數的方法,我們可以稱之為回調函數法。

調用者如何提供函數給dlist_print呢?當然是通過函數指針了。變量指針指向的是一塊數據,指針指向不同的變量,則取到的是不同的數據。函數指針指向的是一段代碼(即函數),指針指向不同的函數,則具有不同的行為。函數指針是實現多態的手段,多態就是隔離變化的秘訣,這里只是一個開端,后面我們會逐步地深入學習。

請看詳細實現過程

6 Don’t Repeat Yourself(DRY)

我見過不少任勞任怨的程序員,別人讓他做什么他就做什么,不管是不是份內的事,不管是上司要求的還是同事要求的,都來者不拒。別人說需要一個某某功能的函數,他就寫一個在他的模塊里,日積月累,他的模塊就成了一鍋“大雜燴”。我親眼見過有程序員在系統設置和桌面兩個模塊里,提供很多毫不相干的函數,這些函數會造成不必要的耦合和復雜度。在這里也是一樣的,求和與求最大值并不是dlist應該提供的功能,放在dlist里面實現是不應該的。為了能實現這些功能,我們提供一種滿足這些需求的機制就好了。熱心腸是好的,但一定不要“管得太寬”,否則就費力不討好了。

7 你的數據放在哪里

對于初學者來說這道題有點難度,很少有人能完全做對。不過沒關系,我并不是要出一道難題來難倒大家,而是要刺激大家去思考,以期達到加深學習印象的效果。有了前面兩次的經驗,我想應該沒人會去寫一個dlist_to_upper函數,大家都會調用dlist_foreach來實現。不過新的問題又出現了,初學者還是有可能犯以下幾種常犯的錯誤。

7.1 轉換大寫的方法不對

這是我們在課本里學到的寫法,但在工程中是不能這樣做的。因為大小寫字母在不同語言中的定義是不一樣的,“a”是一個字符常量,它的值在任何時候都是97,但在不同語言中,97卻不一定代表“a”。我們不能簡單地認為在97(a)—122(z)之間的字符就是小寫字母,而是應該調用標準C函數islower來判斷,同樣轉換為大寫應該調用toupper而不是減去一個常量。

7.2 在雙向鏈表中存放常量字符串,轉換時出現段錯誤。

運行時會出現“Segmentation fault”錯誤。原因是“It”等字符串是常量,常量是不能被修改的。

7.3 在雙向鏈表中存放的是臨時變量,轉換后發現所有字符串都一樣。

運行時發現打印出幾個感嘆號。原因是執行dlist_append時沒有復制一份,所以在dlist中存放的是同一個地址。而且這個dlist在當前函數返回后,里面保存的數據都無效了,因為這些數據指向的是臨時變量。

7.4 存放時復制了數據,但沒有釋放所分配的內存。

這里看起來工作正常了,但存在內存泄露的bug。strdup調用malloc分配了內存,但沒有地方去釋放它們。

初學者對內存和指針只有一知半解的認識,常常犯一些連自己都莫名其妙的錯誤。為了避免這些不必要的錯誤,今天我們要學習各種數據存放的位置以及它們的特性,讓初學者對編程有更進一步的認識。在程序中,數據存放的位置主要有以下幾個。

7.5未初始化的全局變量(.bss段)

通俗地講,bss段被用來存放那些沒有初始化或初始化為0的全局變量。它有什么特點呢,讓我們先來看看一個小程序的表現。

變量bss_array的大小為4M,而可執行文件的大小只有5K。由此可見,bss類型的全局變量只占運行時的內存空間,而不占用文件空間。

現在大多數操作系統在加載程序時,會把所有的bss全局變量清零。但為了保證程序的可移植性,最好能手工把這些變量初始化為0,這樣可以使這些變量都有個確定的初始值。

當然了,作為全局變量,在整個程序的運行周期內,bss數據是一直存在的。

7.6初始化過的全局變量(.bss段)

與bss相比,data段就容易理解多了,看名稱就大概能知道它里面存放著數據。當然,如果數據全是0,為了優化考慮,編譯器會把它當作bss處理。通俗地講,data段被用來存放那些初始化為非0值的全局變量。那么它又有什么特點呢,我們還是先來看看一個小程序的表現。

僅僅是把初始化的值改為非0值了,文件就變為4M多。由此可見,data類型的全局變量是既占文件空間,又占用運行時內存空間的。

同樣,作為全局變量,在整個程序的運行周期內,data數據也是一直存在的。

7.7 常量數據(.bss段)

rodata的意義同樣明顯,ro代表read only(只讀),rodata就是用來存放常量數據的。關于rodata類型的數據,要注意以下幾點。

由此可見,把在運行過程中不會改變的數據設為rodata類型是有好處的。在多個進程間共享,可以大大提高空間利用率,甚至能不占用RAM空間。同時由于rodata在只讀的內存頁面中是受保護的,任何試圖對它進行修改的行為都會被及時發現,這樣一來還可以提高程序的穩定性。

字符串會被編譯器自動放到rodata中,其他數據要放到rodata中,只需要為其加const關鍵字修飾即可。

7.8 代碼(.bss段)

text段存放代碼(如函數)和部分整數常量,它與rodata段很相似,相同的特性我們就不重復了,主要的區別在于text段是可以執行的。

8 棧和堆

8.1棧

棧是用來存放臨時變量和函數參數的。將棧作為一種基本數據結構,我并不感到驚訝;將其用來實現函數調用,也是大家司空見慣的作法。直到我試圖找到另外一種方式實現遞歸操作時,我才感嘆于棧的巧妙。要實現遞歸操作,不用棧不是不可能,只是找不出比使用棧更優雅的方式。

通常情況下,棧是向下(低地址)增長的,每向棧中PUSH一個元素,棧頂就向低地址擴展,每從棧中POP一個元素,棧頂就向高地址回退。這里有一些比較有意思的問題:在x86平臺上,棧頂寄存器為ESP,那么ESP的值是在PUSH操作之前修改呢,還是在PUSH操作之后修改呢?PUSH ESP這條指令會向棧中存入什么數據呢?據說x86系列CPU中,除了286外,都是先修改ESP,再壓棧的。由于286沒有CPUID指令,因此有的操作系統會用這種方法檢查286的型號。

要注意的是,存放在棧中的數據只在當前函數及下一層函數中有效,一旦函數返回了,這些數據也就自動釋放了,繼續訪問這些變量會造成意想不到的錯誤。

8.2堆

堆是最靈活的一種內存,它的生命周期完全由使用者控制。標準C提供以下幾個函數來使用堆內存。

9 小結

本文通過一個簡單需求的完成過程講述了程序員應具備的態度和技能,是程序員進階的必經之路。


本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/454206.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/454206.shtml
英文地址,請注明出處:http://en.pswp.cn/news/454206.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

嵌入式開發——PWM高級定時器

學習目標 加強掌握PWM開發流程理解定時器與通道的關系掌握多通道配置策略掌握互補PWM配置策略掌握定時器查詢方式掌握代碼抽取優化策略掌握PWM調試方式學習內容 需求 點亮8個燈,采用pwm的方式。 定時器 通道 <

解決虛擬機時間引起的奇怪問題

一直使用得好好的虛擬機最近出了一個奇怪問題在虛擬機裝好的lamp在客戶端訪問phpmyadmin的時候,使用firefox登錄沒問題,但是使用IE不行總是停留在登錄的界面,而且沒有提供任何的出錯信息,就連在apache的日志里面也看不到.注意到同樣訪問的時候,在IE上顯示的轉向的url是[url]htt…

TensorFlow 基本操作

Tensorflow基本概念 圖(Graph):圖描述了計算的過程&#xff0c;TensorFlow使用圖來表示計算任務。張量(Tensor):TensorFlow使用tensor表示數據。每個Tensor是一個類型化的多維數組。操作(op):圖中的節點被稱為op(opearation的縮寫)&#xff0c;一個op獲得/輸入0個或多個Tensor…

03_zookeeper偽集群安裝

一句話說明白&#xff1a;在1臺機器上模擬多臺機器&#xff0c;對外提供服務 在理解zookeeper集群安裝方法的基礎上&#xff0c;本文描述如何將1個機器模擬為3個節點的zookeeper集群&#xff0c;建議先參考閱讀本文的前一期 zookeeper偽集群安裝總結 在本機上通過復制的方式&am…

python合成語音_MicroPython動手做(25)——語音合成與語音識別

6、AB按鍵切換語言合成項目[mw_shl_codepython,true]#MicroPython動手做(25)——語音合成與語音識別#AB按鍵切換語言合成項目from mpython import *import networkimport timeimport ntptimefrom xunfei import *import audiomy_wifi wifi()my_wifi.connectWiFi("zh"…

專訪谷歌CEO:像對待家人一樣對待員工

導語&#xff1a;《財富》近日公布了“2012年度美國100家最適宜工作的公司”榜單&#xff0c;谷歌當選冠軍。即將于2月6日出版的美國《財富》雜志印刷版將刊登對谷歌CEO拉里佩奇(Larry Page)的專訪&#xff0c;對谷歌的工作環境進行了介紹。 以下為采訪概要&#xff1a; 問&a…

TensorFlow 分布式

一、簡介 使用單臺機器或者單個GPU/CPU來進行模型訓練&#xff0c;訓練速度會受資源的影響&#xff0c;因為畢竟單個的設備的計算能力和存儲能力具有一定的上限的&#xff0c;針對這個問題&#xff0c;TensorFlow支持分布式模型運算&#xff0c;支持多機器、多GPU、多CPU各種模…

第五周測試

---恢復內容開始--- 一 視頻知識 1 linux系統下如何區分內核態與用戶態 在內核態&#xff1a;cs:eip可以是任意的地址&#xff0c;4G的內存地址空間 在用戶態&#xff1a;cs:eip只能訪問0x00000000—0xbfffffff的地址空間 2 系統調用的三層皮&#xff1a;xyz、system_call和sys…

網頁制作小技巧:dl dt dd標簽用法

< DOCTYPE html PUBLIC -WCDTD XHTML StrictEN httpwwwworgTRxhtmlDTDxhtml-strictdtd> 一般我們在做列表的時候通常只會用到ul和li,至于DL一般都很少用到&#xff0c;它也屬于列表類的標簽&#xff0c;下面說一下大概的用法&#xff1a; <dl>標記定義了一個定義列…

latex公式對齊_Word 寫公式最方便的方法

自從用上了word 2016之后&#xff0c;發現他的公式編輯器真香!真香!!他有了latex的優雅&#xff0c;又有了Mathtype的可視化效果&#xff0c;甚至更好哈&#xff0c;當編輯大量公式時也不會因為插件問題卡掉當前的努力。學起來也不復雜&#xff0c;反正是word. 強烈推薦。我們最…

路要怎么走?關于程序員成長的一點思考

程序員的我們&#xff0c;是否想過今后的路該怎么走、如何發展、技術怎樣提高?其實這也是我一直在思考的問題。下面就此問題&#xff0c;分享下我的看法。因為我閱歷有限&#xff0c;有什么說的不對的&#xff0c;大家見諒&#xff0c;千萬不要噴…… 一、程序員應該打好基礎 …

TensorFlow 常見API

數據類型轉換相關API Tensor Shape獲取以及設置相關API Tensor合并、分割相關API Error相關類API 常量類型的Tensor對象相關API 序列和隨機Tensor對象相關API Session相關API 邏輯運算符相關API 比較運算符相關API 調試相關API 圖像處理-編碼解碼相關API 圖像處理-調整大小相關…

python封裝繼承多態_淺談JavaScript的面向對象和它的封裝、繼承、多態

寫在前面既然是淺談&#xff0c;就不會從原理上深度分析&#xff0c;只是幫助我們更好地理解...面向對象與面向過程面向對象和面向過程是兩種不同的編程思想&#xff0c;剛開始接觸編程的時候&#xff0c;我們大都是從面向過程起步的&#xff0c;畢竟像我一樣&#xff0c;大家接…

將萬億以下的阿拉伯數字轉為中文金額

package test.practice.month3; public class Test005 { //可以不用swich case將123456789轉為一二三四五六七八九 //直接用char[] chars {一,二,三,四,五,六,七,八,九}; public static void main(String[] args) { System.out.println(getCMoney(102030405067L)); } private …

8.2 命令歷史

2019獨角獸企業重金招聘Python工程師標準>>> 命令歷史 history //查看之前的命令.bash_history //存放之前敲過的命令&#xff0c;在 /root/ 目錄下最大1000條 //默認參數值是1000條變量HISTSIZE/etc/profile中修改 //在其中可編輯HISTSIZE參數HISTTIMEFORMAT"…

使用GCC生成無格式二進制文件(plain binary files)

使用C語言生成一個二進制文件 使用自己喜歡的文本編輯器寫一個test.c&#xff1a; int main() { } 再使用如下命令編譯&#xff1a; gcc –c test.c ld –o test –Ttext 0x0 –e main test.o objcopy –R .note –R .comment –S –O binary test test.bin 最后生成的二進…

TensorFlow 實例一:線性回歸模型

代碼 # -- encoding:utf-8 -- """ Create by ibf on 2018/5/6 """import numpy as np import tensorflow as tf# 1. 構造一個數據 np.random.seed(28) N 100 x np.linspace(0, 6, N) np.random.normal(loc0.0, scale2, sizeN) y 14 * x - …

python后端數據發送到前端_Python Django 前后端數據交互 之 后端向前端發送數據...

Django 從后臺往前臺傳遞數據時有多種方法可以實現。最簡單的后臺是這樣的&#xff1a;from django.shortcuts import renderdefmain_page(request):return render(request, ‘index.html‘)這個就是返回index.html的內容&#xff0c;但是如果要帶一些數據一起傳給前臺的話&…

Dapper的基本使用

Dapper是.NET下一個micro的ORM&#xff0c;它和Entity Framework或Nhibnate不同&#xff0c;屬于輕量級的&#xff0c;并且是半自動的。也就是說實體類都要自己寫。它沒有復雜的配置文件&#xff0c;一個單文件就可以了。給出官方地址。 http://code.google.com/p/dapper-dot-n…

函數名作為參數傳遞

假如不知道signal的函數原型&#xff0c;考慮child_handler函數的參數從哪里來&#xff1f; void child_handler(int sig) { if (sig SIGINT) kill(pid_parent, SIGUSR1); } int main(void) { ...... signal(SIGINT, child_handler); ...... } 1、…