對於程式註解的心得


我過去以為寫詳細的註解是正面的行為,應該盡可能這麼做。這幾年下來在實際工作中發現事情不是這樣,首先要承認有很多事根本無法只用註解來解釋,甚至面對面用嘴巴講都說不清楚了。而註解寫好了,同事也常常不會去看,而是直接來問你,我也是一樣喜歡用問的,因為用問的遠遠比自己閱讀要來的快,那麼還有必要寫註解嗎?怎樣的註解才算寫的有價值?怎麼寫比較好?

我憑個人淺薄的經驗以及書籍資料歸納出以下4點:



1. 比程式碼容易讀的註解

當你的註解敘述文字比程式碼還要多的時候,很多人的直覺都是寧願去看程式碼,直接從程式碼看出你的設計手法,這只是直覺,其實有可能還是註解比較好理解,但就是會本能的先去看字數少的那一邊,可見字數是很重要的,忙碌的工程師常常是很猴急的想看重點。

想要節省字數的一個重要方法就是寫虛擬碼,因為一部分的註解依然是用程式碼表示,所以用字會比較少。例如下面的程式上面使用一段虛擬碼當註解,既可減少字數又可以說明你的想法,而不是只讓別人看到不知所云的數字,也不會去亂猜你沒事將x歸零是要做什麼,因為你已經將目的表達出來了。
/* if( 裝置還存在 ) { 銷毀它 } */ if(x) { delete x; x=0; }










在外貌、字數差不多的情況下,你也會比較想閱讀中文對吧?

以前還超常看到這種方塊註解
////////////////////////////////////////// // // // main.cpp // // // //////////////////////////////////////////






可能是「code complete」這本書有特別批評這種寫法,所以現在比較少看到了,這種寫法的缺點就是不好整理,每次修改都要去對齊,有夠煩的,而且也無端的造成閱讀上的負擔,字數越多只會讀的越累,即使寫的東西並不艱澀也一樣,而且需要的編輯手續越多,你就會越懶的去維護。

不過下面這種ㄈ型註解還可以接受
靠文字編輯器的幫忙不會太費事
////////////////////////////////////////// // // main.cpp // //////////////////////////////////////////







2. 不要去說明程式碼已經說過的事

有時候真的是比較雞婆一點,像下面這樣細心的說明
// 開啟*.txt格式的檔案 int OpenFile(const char *name);



雖然沒有註解的話,別人不曉得格式限制,但是看一下範例程式應該就能明白才對,註解只幫了一點小忙,代價是字數又變多了,不划算!不如別寫。


如果想不出字數夠少的註解,那乾脆另外給個文件說明吧,還程式碼一個乾淨的畫面。




3.不要只想靠註解做解釋,程式碼也要協助說明

每個類別、函式、變數的命名也都有輔助說明的作用,只是命名太短太長都不好,有些工程師非常執著於self-documenting的概念,也就是「程式碼即是文件」的作風,僅僅只用函式名稱來說明函式可說是種理想,有些人為了達成這個理想所以函式名稱超級長,幾乎是個完整的句子,那樣也只是造成閱讀困難好嗎。


所以我們對命名的期望是盡量簡短,但是要盡可能足以說明自身用途,想要達成這個目標,基本上有兩個方向:
1.將函式拆分出更小的函式,函式越短你越容易給出恰當的命名。
2.用類別跟成員函式的名稱做聯合說明。

例如
Call(100);



這樣寫根本沒人知道這個函式的意義,但是成員函式的話還有類別名稱可供參考,兩個名字合起來就足以說明了。
phone.Call(100);



一個類別最好只有一個核心目標,圍繞在類別上面的成員就可以用很簡短的命名了,類別名稱也可以變的簡短,因為讀者還可以從眾成員名稱去猜測類別的功能,等於是用類別跟眾成員的名稱來聯合做出說明,字數夠多但是看起來不會繁雜,總之好的架構也會提高可讀性。


有一種常見的糟糕寫法,企圖依賴註解來做解釋,程式碼卻沒幫上忙,如下所示:
/// 這是程式進入點,參數1:執行程式時後面接了多少個字串,參數2:字串陣列 int main(int avg,char **avrg);




參數名稱有寫跟沒寫一樣,目光還要在註解跟程式碼之間移來移去,眼睛會累的啊,為什麼不這樣寫:

/// 這是程式進入點 int main( int count, ///< 執行程式時,後面接了多少個字串 char** strings ///< 字串陣列 );






參數名稱盡量透露自己是什麼東西,而非什麼都交給註解解釋,排版上讓註解直接寫在參數後面不是好看多了嗎?這寫法是doxygen也支援的。

※不過我不會推薦你一定要使用doxygen,現代IDE對閱讀程式碼的輔助支援已經夠好了


4.不是任何說明都要塞到程式碼

其實絕大多數的說明文件都不適合以註解的形式存在,必須另外寫一份規格記錄來說明解釋,真正適合放在程式碼裡的註解都是針對一個物件、函式、變數或者行為來做解釋,或者在註解上告知讀者要參閱哪份文件的第幾頁,而最棒的使用說明文件就是範例程式,所以範例上的註解常常特別多。



"Don’t comment bad code—rewrite it."
- Brian W. Kernighan and P. J. Plaugher

“Truth can only be found in one place: the code.”
- Robert C. Martin

不一定需要執行安裝DirectX SDK


最近的Visual Studio或MinGW都會附上DirectX的函式庫在裡面,所以你可能根本不需要自己準備DirectX SDK了。


如果你需要自己準備的話。
這是DirectX SDK June 2010的安裝程式,在XP上仍然可以使用
http://www.microsoft.com/en-us/download/confirmation.aspx?id=6812


DirectX SDK的安裝程式會順便更新電腦的DirectX版本,不過這並非必要動作,只是要開發程式的話,你可以只取SDK裡的 Include 跟 Lib 這兩個資料夾就好。


安裝程式雖然是附檔名為exe的執行檔,不過還是能用RAR或7zip來開啟安裝檔然後只解開你要的部分。


我個人很喜歡這樣做,因為環境盡量不去更動會比較好釐清問題。


不過現在我傾向於使用SDL或SFML之類的lib來使用了,現在研究只能在 Windows 上使用的 DirectX 顯得不大划算。

In-depth: Functional programming in C++ - 在C++上面使用函數式編程

原文作者為遊戲界的傳奇人物John Carmack


本譯文張貼已經過作者同意
原文網址:
http://gamasutra.com/view/news/169296/Indepth_Functional_programming_in_C.php


譯者:
網路上有很多關於用FP風格寫C++的文章,其中John Carmack所寫的這一篇Functional programming in C++是最讓我起共鳴的,原文在2012年已經有人翻譯過了,不過因為我很喜歡這篇文章,所以用個人方式理解再翻譯一遍。

原諒我為了語意通順,並沒有逐句翻譯,但不用擔心,技術內容並沒有打折扣,這篇文章真的很讚。

因為我的翻譯能力有限,所以在這裡先做一些閱讀前的說明,會讓你讀起來更舒服點。

1.Functional Programming在文中都用FP來簡稱。
2.純函式是FP的中心思想,遵守純函式的規則就可以得到FP帶來的好處了。
3."純"指的是沒有副作用,副作用越少的函式就越"純"。
4.副作用是指函式執行時對外部造成的影響,只有回傳值的影響不算副作用。
5.純函式的行為跟數學上的函式是一樣的。



本文:

也許各位早已聽過一種名為FP的程式編寫思維,它被視為軟體開發者的福音,甚至有人認為FP就是銀彈(註1)。不過查了一下維基上的資料卻讓我倒盡胃口,網頁上劈頭就講了λ演算法(註2)跟形式系統(註3),當下的確看不出來這些理論對軟體開發有什麼幫助。


個人站在實務面所歸納的結論是:軟體開發過程產生的疑難雜症,大多是因為工程師沒有掌握好程式執行過程裡各時期的狀態所導致的。在多執行緒環境下這情況會變的更嚴重(假如你曾留意過這問題)。使用FP思維來編寫程式會讓程式碼的流程狀態變得清楚明確,程式碼也變得更有條理,而且完全遵守FP規則的程式是不可能發生執行緒衝突的。


我認為FP確實有它存在的價值,但是光憑這點就去呼籲大家放棄C++然後改用LispHaskell這些語言是不負責任的發言。


程式語言的設計者總是擔心語法的優點會被外在因素抹滅,而這種事情在遊戲界特別容易發生,我們在專案開發上必須面對跨平台問題、被特定的工具庫、認證門檻、授權技術綁死以及嚴苛的性能要求等等外在因素,再加上還要用有限的人手去維護前人遺留下的程式碼。


如果你的工作環境條件允許你使用非主流語言的話,那麼恭喜你,但也要做好挨罵的心理準備,罪名是拖慢開發進度之類的。


不管你用的是哪一種語言,使用FP風格來撰寫程式都能帶來好處,只要覺得情況允許使用FP就該用,如果覺得情況不適合也要再好好想想為何不用。如果打算使用FP的話你可以去查一下λ演算法、單子(註4)、柯里化(註5)、在無窮集合上組合惰性求值函式(註6)、以及學習其他FP導向的語言。


C++並不鼓勵你採用FP,但也不阻止你使用,而且C++允許你深入底層使用平行處理的指令集來操作記憶體上的資料,還允許你使用其他你需要的強大功能。


註1:銀彈是傳說中可以有效傷害吸血鬼的武器,在IT界則用銀彈比喻對軟體開發有卓越效果的技巧。
註2:λ演算法(lambdas)思維就是將function當參數或回傳值使用。
註3:形式系統(formal system),用於邏輯推導的一種主義思想,直接想成是數學上的邏輯推導就行了。
註4:單子(monad)是FP裡面提到的一種抽象型別,用來表示一段計算而非數據資料。
註5:柯里化(Currying)就是把一個多參數的函式包裝成一個只接受一個參數的新函式,舉例就是把 f(x,y,z)=x+y+z 包成 g(x)=f(x,3,5)
註6:惰性求值的意思是算式執行的當下沒有真的去計算答案,不儲存結果只儲存算式,然後等到該答案真的需要使用時才開始做計算。(這麼說我以前就會惰性求學跟惰性寫作業了)



純函式

一個純函式只注意外部傳進來的參數,它唯一的工作就是依據輸入參數求出回傳值,邏輯上不會有副作用,當然我指的是抽象概念上的副作用,以硬體角度來看,任何函式都有副作用,但在抽象角度上它的確沒有副作用。

純函式不會去讀寫全域變數,內部不保存靜態變數,不進行IO操作,不修改傳進來的參數,最理想的狀況是連那種有可能跟外部連動的參數都不要傳進來,像是傳遞全域變數的指標進來就偏離純函式的宗旨了。

純函式有以下優良特質:

*執行緒安全性
一個使用實體參數的純函式是完全沒有執行緒問題的,但如果使用指標或參考作為參數的話,你有必要注意一下其他執行緒有可能也引用一樣的指標來操作同一塊資料,甚至釋放掉記憶體。即使避不開這樣的風險,純函式依然是能讓多執行緒更加安全的有效技巧之一。

你可以簡單的把這些函式拿來平行執行或者個別執行比較結果,這樣測試跟展開函式會安全很多。


*重覆使用性
要將純函式移植到其他環境非常容易,雖然還是要處理參數型別問題以及轉接內部呼叫的其他純函式,但是至少不會牽一髮而動全身。你不知道有多少程式碼從舊架構環境中抽出來時,所花費的時間比重寫一個新的還要久。


*可測試性
純函式具有引用公開化的特質,意思是每次輸入同一組參數都會得到相同的結果,這使得純函式比糾結的程式更容易做調試。


我向來都盡寫些不負責任的測試程式,程式中有太多地方跟系統相連了,需要搭配複雜的配套措施進行測試修改,我總是認為這不值得花時間去寫(也許我這想法也不對)。

純函式可以方便你做細部測試,測試碼看起來就跟教科書上寫的一樣漂亮。每當遇到結構刁鑽的程式時,我會切割成一個個純函式來分別做測試,驚人的是,常常還是能測出小毛病來,這意味著我佈下的防護網還不夠周全。


*可讀性與可維護性
因為參數輸入跟輸出的直接影響範圍很有限,你可以很容易搞懂以前寫的純函式,跟函式外部有關的潛規則也變的更少。


形式系統跟程式自我推理(註7:)在未來會越來越重要,靜態程式分析在現今就已經很重要了,程式寫得越符合FP規範,程式分析工具會運作的更好,不然至少也會讓速度快的局部分析工具的分析範圍變得更大,分擔更多全域分析工具的工作。
我覺得對於Eclipse之類的工具而言,OO跟FP都能分析的很好,差別在於OO的寫法如果封裝不好的話會很難閱讀的,當你要追查一個變數的影響範圍時,OO會追的比較辛苦。


我們這領域重視的是"完成品",架構正確性的形式證明(註8)還沒被列為開發重點,但是去證明有哪些還沒浮現的潛在危險仍然是值得的。我們可以在開發過程中應用更多的學術理論。


正在修計算機概論的同學可能會一邊抓著頭一邊想:"程式不都是這麼寫的嗎?",現實中卻是搞成"大泥球"(註9)的專案比較多,傳統的指令式程式語言提供了緊急應變手段,結果大家沒事就拿來用。如果你寫的是免洗程式,那倒無所謂,一直用全域變數也沒差。


如果你寫的是過了一年都還會用到的程式碼,那就要衡量一下是現在方便重要,還是避免日後一定會發生的問題重要。大部分軟體開發者都沒有用長遠的眼光去預測修改程式引發的麻煩。



註7:自我推理(automated reasoning)是AI領域的名詞,指的是電腦能自己進行邏輯推理。
註8:形式證明(formal proof),就是高等數學裡的論證方式,這沒什麼好翻的。
註9:大泥球(Big Balls of Mud)是一種反面模式,指的是程式結構混亂不清晰這種常犯的錯誤。



純度的實現

並不是每個地方都可以純函式化,除非整個程式都是自己親手寫的,不然總有些地方必須跟外界交流。盡力去提昇程式的純度(註10)是很有趣沒錯,但是在實作上必須承認在某些情況下,最低限度的副作用是必要的。


即使針對單一函式而言,純度的實現也不是那種會功虧一簣的工作。純度的價值隨著純度的提昇只會越來越高,而且從"一團亂"提昇到"大致上純化"所得到的好處比從"幾乎純化"提昇到"完全純化"還要高。即使無法達到完全的純化,也應當盡可能提高純度。使用全域計數器或全域旗幟是拉低純度的行為,不過如果除此之外都已純化,那麼還是能得到純函式化所帶來的好處。


修正大範圍中最糟糕的缺點通常會比雕琢幾個完美的小區塊還要重要。回想一下你碰過最棘手的功能面問題或者系統架構問題,我幾乎能篤定問題是起源於複雜的狀態溝通網絡,也許程式的錯誤行為是受到這些狀態的牽動,而且影響到的不止是參數而已。在發生問題的區塊加強管制,或至少拼命防止更多程式陷入類似的麻煩,做這種事比你花時間去最佳化底層的那些數學函式庫還要有意義。


朝著純化進行重構的過程裡通常會出現將一段算式做分割的行為,這十之八九會產生更多負責參數傳遞的程式碼,字數冗長的程式可是公認的差勁寫法,但FP的結果卻常常反而是減少了程式的字數,我知道這聽起來有點弔詭。

FP寫法之所以在某些情況下比指令式語言更加精簡的原因跟這些有關:純函式的使用、垃圾回收機制、強大的內建資料型態、模式匹配、條列式推導(註11)、函式編成、多種語法糖(註12)諸如此類。其實這些縮短程式碼的手段大多在指令式語言也找的到,並非FP特有。


如果只是呼叫個函式也要你填十幾個參數,不爽是正常的。可以試著重構程式來減少參數。


C++對純度維護沒有提供任何支援,這點不大理想,假如有人污染一個被大量呼叫的純函式,那麼所有呼叫該函式的純函式也會失去純度,這問題對形式系統來說很嚴重,但還是那句話,維護純度不是那種會功虧一簣的工作,破壞純度也不是說罪無可赦,對整體的程式開發而言,失去純度就只是有點可惜而已。


聽起來C/C++應該在新的標準裡增加pure這個關鍵字,目前已經有一個類似的關鍵字叫做const,一個用來讓編譯器幫忙監督、保護工程師的選項,而且這招常常是管用的,D語言已經有pure這個關鍵字了,請注意其中弱純度跟強純度的之間的差異,強純度的指標參數也需要加const關鍵字。


從某些角度來看,用關鍵字來強制要求純度是很狹隘的設計,純函式即使呼叫了不純的函式,只要沒有帶給外部副作用就還是很純。只接受命令列參數不讀取其他檔案的程式也可以視為一種純函式。


註10:純度(purity),我想不到什麼好的翻譯,因為原文名詞一樣令人費解,總之就是純函式化的程度。
註11:條列式推導(list comprehension),好的,這也是我亂翻的,這種寫法的程式看起來就像數學算式一樣全擠到同一行,根本是FP作風。
註12:語法糖(syntactic sugar)又稱糖衣語法,是程式語言設計者提供給你的一些偷懶語法。



物件導向設計

OO將經常變動的部份封裝起來以提高可讀性,FP則是縮短經常變動的部份以提高可讀性
- Michael Feathers(@mfeathers)


所謂"變動的部份"其實是指"變化的狀態",物件導向入門書籍最先教的就是操作物件改變自己的狀態,這對大部分軟體工程師也是非常根深蒂固的概念,但這行為跟FP是背道而馳的,顯而易見的,OO將函式跟變數封裝在一起是有它的價值的,但是要讓一段程式採用FP設計就必須捨棄某些OO特徵了。
這裡並不是說採用FP之後就無法貫徹OO了,這兩者其實是可以一起使用的武器,沒有人會嫌自己武器太多的


不能宣告為const的成員函式在FP定義上就已經不純了,因為這種函式會改變物件的狀態,也不是執行緒安全的,這種讓物件狀態漸漸失控的設計顯然是bug的主要來源。


如果被C++隱藏的物件指標"this"不算參數的話,宣告為const的成員函式在技術上也算是純函式。但是當物件規模大到讓成員變數用起來像全域變數時,純成員函式的好處會被埋沒。建構子也可以寫成純函式,而且最好都這麼寫,寫成一種輸入參數然後回傳物件的純函式。


就策略上,你可以用FP的思維來使用物件,也許還需要改一下介面,在id Software工作的時候,我們有個使用長達十年的向量類別叫做idVec3,它有個能將自己標準化的成員函式( void idVec3::Normalize(); ),但卻沒有一個同功能的全域函式( idVec3 Normalized(); )來產生標準化的向量類別。很多字串容器也是用類似的設計,它們修改自己的內容,而不是回傳一個修改過的副本,例如ToLowerCase()、StripFileExtension()等等。
考量到記憶體的配置時間,直接重複利用原本的記憶體是合理的,只是你必須確定該記憶體已經沒其他人在用了,才能回收使用



性能影響

絕大部分情況下,直接修改記憶體的資料是程式執行效率最高的做法,而且可以避免浪費效能。但是這只不過是理論上的空想罷了,現實中我們總是選擇犧牲效能來換取開發效率。


使用FP會導致更多資料複製行為,在一些情況下,基於性能考量FP反而是錯誤的策略。舉個極端的例子,你如果寫個用來改圖的純函式,它接受整張圖片作為參數複製進來,然後改好之後回傳改好的全新圖片,千萬別這麼做。


回傳實體變數是FP的編程風格,但是依賴編譯器做回傳值最佳化會有性能上的疑慮,改成傳遞參考來輸出複雜的資料結構可以避開這問題。但是這又帶來新的問題,你就算加上const關鍵字也無法讓回傳值遵守單賦值(註13)。
這裡我不懂為何參考加上const關鍵字無法遵守單賦值,另外C++11新增了std::move()這樣的工具,有機會降低傳遞變數時的開銷


在很多情況下會傾向更改結構變數中的某個成員而不是寫一份新的結構,但這會失去執行緒安全性,不該輕易這麼做。但list這麼做倒是挺合理的,遵守FP規則的list用法一樣是複製一份新list回傳,原本的list則原封不動。真正的FP語言會有特別的實作來實現這功能,所以沒有聽起來那麼嚴重,但是C++的容器也這麼做可就慘了。
這邊的list不知道該翻成什麼,可能是FP的專有名詞吧 



這裡有個可以緩頰的好理由,那就是如今追求執行效率都是針對平行運算的程式設計,相較於單執行緒程式,多執行緒就算是最佳化也通常需要更多的複製、合併行為,所以相較之下代價變小了,換來的好處是複雜度的降低、正確性的提升。


當你開始思考如何讓遊戲世界的所有角色同時動作時,你會馬上陷入因為採用物件導向而產生的多執行緒難題。也許可以規定所有物件都只能讀取全域狀態不能進行寫入,然後在繪圖迴圈跑完一圈的時候去讀取更新過的全域狀態,嘿!給我等一下...
因為大家都唯讀的話就沒有人可以去更新狀態了



能做的事

調查你程式中比較有規模的函式,追蹤所有跟它們有關的外部狀態以及所能造成的影響,即使你根本沒用這函式做什麼事情都還是會寫出一份龐大的註解。如果最後發現這函式的副作用居然還能影響螢幕畫面,那你可以舉雙手投降然後宣稱這函式已經算超自然現象了。


你的下一個任務是從源頭開始思考程式真正的運算結果,整理輸入的參數資料然後傳給一個純函式處理,接下來用這純函式的回傳值做點什麼。

當你除錯的時候,多留意背地裡變化的狀態跟隱藏參數在做什麼。

修改你的類別實作,讓它可以回傳一份新的複製品而不是修改自己的內容,試著把每個非累加變數都宣告為const。

其他參考資料:

http://www.haskell.org/haskellwiki/Introduction

http://lisperati.com/

http://www.johndcook.com/blog/tag/functional-programming/

http://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf

http://channel9.msdn.com/Shows/Going+Deep/Lecture-Series-Erik-Meijer-Functional-Programming-Fundamentals-Chapter-1

http://www.cs.utah.edu/~hal/docs/daume02yaht.pdf

http://www.cs.cmu.edu/~crary/819-f09/Backus78.pdf

http://fpcomplete.com/the-downfall-of-imperative-programming/


註13:單賦值(single assignment),把變數宣告成const就能符合單賦值,因為能防止不小心又對變數賦值。


讀後感:

靠近底層的程式會跟裝置緊密結合,而裝置就是一個會記住設定的狀態了,互動程式也一定需要留住許多外部輸入的狀態,所以遊戲程式有很多地方不能實施FP,但如同John Carmack所言,能用FP就盡量採用。

FP其實並不是用來批評、取代OO的,但我覺得玩OO玩到走火入魔的人剛好可以學FP來均衡一下,而且藉此檢討一下自己的OO有哪裡寫的不好,例如物件內部保存的狀態應該越少越好,這樣變化較少,也比較容易維護測試。物件跟物件之間的瓜葛應該越少越好,互動上越清晰越好,別讓物件背著你做出超出預期的行為。懂OO的缺點就會懂FP的優點。

別認為C語言會比物件導向的C++更適合實現FP,純函式會將其他函式做包裝再回傳,這種事要用boost::function才做得到(C++11的匿名函式更加適合實踐),C++其實比C更適合FP。

只使用命令列來執行MinGW以及Visual Studio 2010

習慣了Linux底下敲指令執行GCC的方便性之後,變得不大能忍受Windows下開啟IDE的等待時間,所以查了一下如何在Windows下使用command line來編譯程式。


MinGW

很簡單,寫下面3個批次檔,放在專案資料夾就可以了(Makefile的所在位置)。


command.bat
cmd





make.bat
mingw32-make





clean.bat
mingw32-make clean






用滑鼠點開command.bat,然後在跳出來的命令列視窗裡打上make,感覺跟在Linux打指令一樣。


command.bat
用來取代"開始->執行->cmd"的這套流程


make.bat
當mingw32-make的別名,由於mingw32-make無法用tab鍵補齊名稱,所以才多寫個make.bat來偷懶,讓你可以跟在Linux底下一樣直接打make就好。


如果只想用滑鼠點開make.bat單純看結果,只要在mingw32-make下一行補上pause就行了。

Visual Studio

這方法能對哪些Visual Studio版本起作用我不知道,我目前只試過Visual Studio 2010跟Visual Studio 2015。


先去尋找你的msbuild.exe位置,然後將路徑加入環境變數PATH

Visual Studio 2010使用的msbuild.exe放在這裡
C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319

Visual Studio 2015使用的msbuild.exe則是放在這裡
C:\Program File (x86)\MSBuild\14.0\Bin
並且需要新增一個環境變數VisualStudioVersion設值為14.0


接下來的工作很類似


command.bat
cmd





make.bat
msbuild.exe your_project_name.sln /p:Configuration=Release





clean.bat
msbuild.exe your_project_name.sln /t:Clean






如此就不用等待Visual Studio的漫長開啟時間了

另外要rebuild也是可以的
msbuild.exe your_project_name.sln /p:Configuration=Release /t:Clean,Build
msbuild.exe your_project_name.sln /p:Configuration=Release /t:"Clean;Build"

CodeBlocks跑好慢啊

最近在 Windows 上面跑 CodeBlocks 變很慢
在開啟新的程式碼文件時會卡很久
似乎跟專案越來越大有關
第三方函式庫如果別加入專案應該會好很多(事先就先編譯好 library)
Ubuntu 上的 CodeBlocks 就沒這情況

Eclipse 一直都是啟動慢
但是過了啟動那段就很順了

唉~
只好讓 CodeBlocks 坐板凳了

更新完java之後就打不開Eclipse

A Java Runtime Emvironment (JRE) or Java Development Kit (JDK) must be available in order to run Eclipse. No Java virtual machine was found after searching the following loactions D:\eclipse\jre\bin\javaw.exe jacaw.exe in your current PATH

在環境變數PATH補上下面這句就好了(當然你名字要改一下)
C:\Program Files\Java\jre1.x.x_x\bin


視情況也許Eclipse需要重裝,反正是免安裝程式,並不麻煩。

我現在每次更新Java都因為C:\Program Files\Java\jre1.x.x_x版號會變,所以每次都需要編輯環境變數PATH。

遊戲相關工具


這邊收集一些遊戲開發上的工具
沒特別說明的話就是以C++為主要開發語言
最後更新2017/10/19

SFML
跟SDL是相同類型的工具,一口氣跨了眾多平台,介面設計的非常好學。

GDevelop
以SFML為基礎來開發的開源工具,可以靠拖拉元件來製作遊戲,有提供免安裝版本,不支援移動平台。

Godot
跨了各種平台的強大開源遊戲引擎,可以當它是open source的Unity。

Corona
採雲端編譯的手機遊戲開發工具,不需要準備各種手機的開發環境,使用者只需要用lua來設計自己的遊戲便可,免費使用,唯有發佈到官方平台上才需要收費。

KlayGE
中文開源社區的作品,跨了各種平台,作者不時的發表3D繪圖技術文章。

Urho3D
以SDL為基礎,集結了bullet、lua等許多厲害的第三方工具,值得考慮的開源遊戲引擎。

Scirra
用來製作HTML5的遊戲,不用寫程式碼,拿來發表商業作品需付費。

OpenSceneGraph
一個OpenGL的高級wrapper,有點偏學術性質,有自己實作跨平台視窗,也可以配合GLUT、SDL、SFML、QT等等跨平台視窗來使用。




https://alternativeto.net/
這個網站可以讓你尋找同類型的工具,當你對目前使用的工具不滿意時,可以來這裡看看,很有參考價值。

順帶一提,尋找Github專案的方法不是只有在google搜尋github name這樣而已。
Github現在有提供topic這樣的功能,你可以在Github的搜尋欄輸入topic:name來尋找。
例如輸入topic:lua來尋找有使用lua標籤的專案。


用萬國碼寫C++程式


首先我先給個簡單的結論
「盡量使用UTF-8,不管是在Windows還是Linux」

寬字元這個字眼泛指UTF-16以上的編碼方式(UTF-16、UTF-32等等),UTF-8不用寬字元來儲存,使用C語言原本就有的char便可。


sizeof(char)==1
sizeof(wchar_t)==2 Windows下的寬字元
sizeof(wchar_t)==4 Linux下的寬字元


使用UTF-8的情況
char *str= u8"ToyBox" u8"玩具"
sizeof(str) 7
(6個字母加結尾)
5
(兩個中文字加結尾)
strlen(str) 6
(正確)
4
(誤判了)











在UTF-8裡面,一個中文字元佔2byte以上,所以不能用strlen()來判斷字數。

※如果你在程式碼寫 L"玩具" 會當成是萬國碼寬字元
而只寫 "玩具"前面沒補上L的話會被GCC當UTF-8處理
但是 Visual Studio 是依OS語系來轉換,台灣這邊就是當成BIG5編碼了
C++11有提供新的寫法 u8"玩具" 來指定編譯器要以UTF-8處理


下面是Windows下的寬字元


Windows下的寬字元
wchar_t *str= L"ToyBox" L"玩具"
sizeof(str) 14
(每個字元都佔2byte)
6
(兩個中文字加結尾)
strlen(str) 6
(正確)
2
(正確)











上面只是個例子,萬國碼有可能一個字就超過2byte,所以連Windows下的 wchar_t都無法正確計算字數,所以如果想要讓每個字都佔有相同的大小,你必須選擇UTF-32才夠大,使用寬字元的好處就是計算字數很容易,要個別讀字碼也很輕鬆。 


UTF-8的最大好處在於它可沿用ansi key code的處理方式,像 fopen() 就不用改成 wfopen() 這怪樣子(更別說不同的編譯器可能還會有不同的名字),所以不支持萬國碼的舊版lua照樣可以接受UTF-8字串。

UTF-8有分「檔頭含BOM」跟「檔頭不含BOM」兩種
含BOM這種是微軟想出來的
不含BOM這種才是真正的UTF-8


大致上比較起來,不含BOM會是比較好的選擇,但可能是為了配合微軟,大多數場合對於含不含BOM並不在意,像是python、gcc、lua都不介意


帶有BOM的UTF-8 沒有BOM的UTF-8
缺點 1.CMake的組態檔不接受這種編碼
2.php也不吃這個
3.即使只寫英文也無法被當成ansi文件
4.NetBeans的Breadcrumbs會失效
1.這種程式碼在Visual Stduio不能除錯









最後誠心建議以UTF-8為主就好,我過去寫的程式為了兼容UTF-8跟寬字元真的多寫了很多東西,現在都以UTF-8為主,必要時才轉為寬字元。

lua API



堆疊頂部元素索引為-1
堆疊底部元素索引為1

最新push進去的元素是位在索引-1

lua的特有結構table可以當做std::map來看待就好,使用
上都是key-value的概念


lua_pushvalue(lua_State *L, int index)
將該索引位置的元素複製到堆疊頂部

lua_pop(lua_State *L, int num)
將頂部num個元素刪掉

lua_getglobal(lua_State *L,const char* name)
從全域table取出指定名稱(key)的成員

lua_setglobal(lua_State *L,const char* name)
將堆疊頂部元素取個名字然後塞進全域table

lua_getfield(lua_State *L, int index, const char* name)
從指定索引(table)取出指定名稱(key)的成員,這不是一種複製,對取出成員所做的行為會執行在指定索引的該成員

lua_setfield(lua_State *L, int index, const char* name)
將堆疊頂部元素取個名字然後塞進指定索引(table)

lua_gettable(lua_State *L, int index)
將該索引位置的元素(table)複製出來,取代堆疊頂部元素,原本的頂部元素則成為table成員

lua_settable(lua_State *L, int index)
將索引-1當作value,索引-2當作key,塞入指定元素(table)

lua_newtable(lua_State *L)
新增一個空table到堆疊頂部

lua_pushnil(lua_State *L)
新增一個空值到堆疊頂部

lua_insert(lua_State *L, int index)
將堆疊頂部元素插進指定位置

遊戲人的心得錦集


下面收集職業、業餘遊戲開發者的心路歷程,國內唯一以心得出一本書的遊戲人大概就只有半路了,下面這份名單會不定期更新。

最後一次更新2015-10-21


猴子靈藥的中肯入行忠告
前進遊戲界:給大學生的行前準備建議

"前進天空樹"的製作人
給想全職做遊戲的你

雖然不是出自遊戲開發者之手,但是非常適合有興趣的學生
給大學生的忠告:把興趣當工作,不會帶給你快樂

NDark在元智大學的演講稿
遊戲製程心得二三事

Chris Crawford所寫的
頂級遊戲設計大師談如何成為一名遊戲設計師

興趣跟工作兩者之間本來就有著截然不同的性質,但是對社會新鮮人而言,常常覺得做自己喜歡做的工作才是理想目標,不希望將青春花在不喜愛的事物上,不希望為了賺錢而賺錢,希望往自己的興趣發展也能闖出一片天。


這沒什麼不好,親自去試過才會知道自己是怎樣的人,才會知道自己究竟能不能忍受逐夢的辛苦,究竟是不是真的不在乎薪水,這原本是畢業之前就該搞懂的事,不過長輩大多在你求學期間只會跟你說「先把書讀好」、「考到一所好學校再說」之類的,也許連這些出社會的大人們也不清楚自己的目標,如果沒搞清楚自己的方向,跑再遠都是白搭。


想要找適合自己的道路就必須先好好的認識自己,先尋找自己的起點才不會迷失方向。

我自訂的設計模式


2016/08/10
這篇提到的做法已經被我捨棄了,物件之間有依賴關係時,使用 std::shared_ptr 處理就已經很理想了。



在開發ToyBox途中,我設計了一個簡單的設計模式,用來解決我擔心的某種資源洩漏狀況。



很多情況下,兩個物件可能在資源上有依賴關係,需要互相保持聯絡,注意對方手上的資源是否還存在,當其中一方被銷毀或者打算切斷關係時,有必要通知對方一聲,讓對方可以處理一下(不要說一直到打了電話才知道這個人已經切斷關係),這可以避免許多記憶體問題,算是一種制式化的觀察者模式,只觀察對方是否打算斷絕聯絡,彼此互相觀察。


實作上就只是互相記住對方的指標,然後準備好一個callback function,上面紀錄當對方斷線時該如何應對,當對方斷線時就會由對方呼叫這個callback function。


實作寫在 ToyBox/include/toy/Link.hpp,大概要用一段時間才知道這作法的優缺點,有可能到頭來我只發現這作法是多此一舉也說不定,如果你對這個模式有什麼更好的建議,歡迎留言或來信告知。


#include <stdio.h> #include "toy/Link.hpp" //當 obj_B 主動絕交時會呼叫這個 obj_A 預先準備的行為 void obj_A_reaction() { printf("goodbye obj_B\n"); } //這範例是 obj_A 先斷交的,所以只有執行這個 void obj_B_reaction() { printf("goodbye obj_A\n"); } int main() { toy::Link obj_A; toy::Link obj_B; obj_A.SetReaction(&obj_A_reaction); obj_B.SetReaction(&obj_B_reaction); obj_A.Connect(obj_B); //兩者連結上了 obj_A.Disconnect(); //由obj_A主動斷交,此時只會呼叫obj_B_reaction() return 0; }

成員函式的callback function


C語言的callback function做法只能用函式指標去儲存全域函式,在C++中要用同一招會顯的很不自然,因為全域函式無法封裝到類別當中,而member function則無法直接傳遞函式指標,因為沒連帶物件指標一起傳是沒用的。



於是boost用樣板技巧創造出可以呼叫成員函式的「仿函式」,它將成員函式跟物件的指標封裝在一起,呼叫的人並不需要知道這boost::function的內容物是哪裡來的,裝的是全域函式還是成員函式也不用知道,甚至連這函式實際上到底需要多少個參數也不知道,非常全面的取代了 C語言的 callback function,你只要知道它可以取代函式指標,尤其想實現成員函式的callback function只剩這條路可選。


不過需要注意「仿函式」是用樣板實現的,而樣板魔法只能實作在標頭檔上面,這也不算什麼缺點,跟它帶來的好處相比實在太划算了。


仿函式歷經長時間的演化,已經成為了非常強大的好用工具,在C++11裡面已經成為標準庫成員了。


我的專案裡有個山寨版的仿函式實作,大多功能都有實現了,內容會比boost的原作好懂許多
,還滿有趣的,尤其bind的實作手法相當精彩,運用了繼承、樣板偏特化、多載,感謝boost高手們的貢獻,如果看不懂實作的話無所謂,畢竟那些樣板技巧很少用到。


#include <stdio.h> #include <functional.hpp> void MyFunction(int a) { printf("%d\n",a); } int main() { using namespace std::placeholders; // for std::placeholders::_1 std::function<void(int)> func=std::bind(&MyFunction,_1); func(5); return 0; }

lua與C++之間的互動:如何在lua裡面建立C++的物件


首先lua並不支援你將Class傳到script這邊,lua只支援將一個C風格的全域函式指標傳過去,因此一切行動都是從全域函式開始的。

基本概念就是將C++的一組全域函式傳到lua這邊,然後用lua的table將它們包裝的像個物件一樣,table會內含一個物件指標,這就是大致上的實作方向了。


首先在C++這裡寫個全域函式負責動態配置物件,將它的函式指標傳到lua之後會扮演建構子的角色,產生的物件指標會用lua的userdata來儲存。


另外再寫幾個全域函式來負責操作物件指標,它們的函式指標傳到lua之後會扮演成員函式的角色。

將上面這些函式裝到lua的table上面就可以直覺的使用C++的物件了。


解構子則不需要以上做法,在儲存物件指標的userdata上面就可以指定解構行為了。這裡需要注意一點,lua的解構時間點跟C++不同,因為lua的垃圾收集機制不是當變數無人使用時就會去清理,所以不要以為區域物件在離開該區域時就會當場解構,而且由於指標的size太小,lua根本不知道指標背後咬了多大一塊記憶體,可能會累積超多物件才解構,你最好自己親自呼叫"__gc"來解構,或者用collectgarbage("collect")強制要求立刻清理。


要傳給lua使用的全域函式有固定的格式(lua_CFunction),而且從lua取得參數以及送回傳值給lua都需要呼叫lua介面來執行,你可以看到網路上有很多教學是自己寫個Wrapper類別來當中間層,然後為類別量身打造一系列全域函式,這樣寫起來會很累,而且不能封裝的全域函式會破壞OO架構,程式碼不會好看到哪去。


幸虧C++還有樣版這樣的特別武器,可將上述手續給包裝起來,真的輕鬆超多的,我的luapp專案就是在做這件事,luapp已經將這份工作包裝的很舒服了。


當然檯面上還有其他更成熟的專案提供更全面的功能,不過以我的需求來說,自製的luapp已經堪用了。

CMake指令



CMake的組態檔總是命名為"CMakeLists.txt"
CMake的語法是由一堆函式構成
不管做什麼都是在呼叫函式
以下列出比較常用的


project(專案名稱)
你的專案檔生出來就會用這個名字

cmake_minimum_required(VERSION 2.8)
確認該環境的CMake版本符合你的要求,CMake會強迫要求你在主專案加上這行,子專案就沒關係

add_subdirectory(子資料夾)
進入指定資料夾然後執行裡頭的組態檔"CMakeLists.txt",這設計讓你很容易增加子專案

include_directories("路徑")
告訴CMake該去哪裡尋找標頭檔

link_directories("路徑")
告訴CMake該去哪裡尋找函式庫

add_executable("執行檔名字" main.cpp main2.cpp main3.cpp)
決定要編譯哪些程式碼,然後產生程式

set(EXECUTABLE_OUTPUT_PATH "路徑")
決定編譯出來的執行檔要放哪裡

add_library(函式庫名稱 STATIC main.cpp main2.cpp main3.cpp)
決定要編譯哪些程式碼,然後產生函式庫,STATIC表示你要編譯成靜態函式庫,動態要寫SHARED

set(LIBRARY_OUTPUT_PATH "路徑")
決定編譯出來的函式庫要放哪裡

target_link_libraries(函式庫/執行檔名稱 需連結的函式庫名稱)
決定要連結的函式庫,執行檔跟函式庫都用這個設定

message("想傳達的訊息")
CMake允許你在生成專案檔的過程印出一些訊息

CMake的入門教學


CMake有個很棒的概念就是「擺放程式碼的資料夾」跟「擺放生成物的資料夾」是分開來的,下面簡稱這兩個資料夾為「src dir」跟「bin dir」,專案資料夾就是「src dir」,而你需要另外準備一個空資料夾來做為「bin dir」。


「bin dir」可以開好幾個無所謂,面對各種編譯器你可以各準備一個「bin dir」來負責,靜態函式庫跟動態函式庫也可以分別用不同的「bin dir」,CMake生出來的專案檔、專案檔產生的函式庫跟執行檔都會放在「bin dir」。


打從一開始就不會去汙染「src dir」(專案資料夾),這比SCons的清理機制更讓人喜歡,CMake的清理就只需要刪除「bin dir」。


有些專案設計沒讓CMake的組態檔放在專案根目錄,改以Makefile或者python來主導一切,讓「bin dir」固定在某個資料夾位置,這會讓CMake失去上述優點,真的滿可惜的。


打開CMake之後最先需要設定的就是上述的兩個資料夾
Where is the source code:「src dir」
Where to build the binaries: 「bin dir」


完成後按「Generate」來設定要用哪個編譯環境,「Configure」按鈕是用來設置其他選項的,通常用不到,我目前會按「Configure」的情況只有在CMake找不到編譯器位置的時候。


想看簡單的CMake範例可以去看functional這個專案,只會產生一個執行檔,連函式庫都沒有產生,結構單純,我在上面有加中文註解,會很好理解。

看懂之後可以去看luapp的CMake寫法,那是比較像樣的專案結構。

最後才看ToyBox裡面所寫的,我喜歡的CMake技巧都應用在上面了。

對於CMake的基本指令就收集在這裡

用git下載專案


這裡只針對完全沒碰過git的人,簡單說明如何用git下載專案。

在下載安裝完git之後,找個你要擺放專案的資料夾,按滑鼠右鍵,選Git Bash開啟一個命令視窗,這命令視窗的指令跟Linux下的基本指令是一樣的。


輸入下面指令來下載專案
git clone [專案位址]

以我的專案為例
git clone http://github.com/ToyAuthor/ToyBox.git


此後你可以進去專案資料夾,用下面指令檢查作者有沒有更新
git remote show origin

顯示<up to date>就是沒有更新,你手上的版本已經是最新的了,若有更新就用下面指令取得最新版本
git pull


Unlicense也是一種協議?


http://unlicense.org/
原本以為"不保留著作權"這件事並沒有人特地設計一份協議來表達的,還以為Open source最慷慨的協議是MIT跟BSD這一類的,最近才知道還有特定用來捨棄著作權的聲明。

Unlicense如字面所言,它並不算是License,而是一份宣稱捨棄著作權的放棄聲明,放棄之後就屬於Public domain了,跟創用CC授權中的「CC0」是一樣的意思,一樣將著作完全放入公共領域,任何人都可以擁有但不能獨佔。

過去的授權主流是GPL,現在的話我不曉得,至少MIT已經是Github最多人採用的協議了,很多過去閉源的著名專案也開放了,更多的自由、更少的限制是種趨勢了。

Ogre3D就有提到人們要回饋分享程式碼完全是出於自願的,用License來強制規範是沒有用的。(現在Ogre3D已經改用MIT發佈了)


如果對各種協議不是很清楚它的規範的話,可以到這網站查詢,它有非常簡明的描述。

目前ToyBox就是採用Unlicense發佈到Public Domain。