查看完整版本: 如何寫出讓別人好維護合修改的程式?
頁: 1 2 [3]

chevylin0802 發表於 2016-1-5 09:49 AM

本帖最後由 chevylin0802 於 2016-1-5 09:52 AM 編輯

kwj 發表於 2016-1-5 12:18 AM static/image/common/back.gif
有很多 coding style 的習慣,其實是基於當時的 IDE 特性去設計的
例如樓上 chevylin0802 大提到的 { } 要 ...
許多RD其實近幾年常常都把螢幕豎起來看程式碼
就是因為螢幕行數限制的問題
我還有看過有些人的程式
{....} 內搞了好幾百行
像這種程式要看得懂還真的是不太可能
當然
我也相信那是歷次維護的過程不斷新增出來的程式碼
才會搞得又臭又長

只是我不會覺得現在的工程師怎麼都搞得讓人很傻眼
幾乎都是為了讓它可以動而做
甚少考慮到未來接手的人的問題

coding style倒不是為了IDE在設計的
這點真的不是如此
而是在程式語言發展到一定程度之後
有些人會為了他自己讀程式方便而改的
確實完全跟IDE無關
畢竟IDE多數都會整合除錯功能
那些無法設成breakpoint或者雖然可以設成breakpoint卻不會真正停留在那一行的情況
其實不會是IDE提供的

當然關鍵字會變色的部份
已經是很普遍的一種IDE具備的功能了

其實就算是vi也一樣有這種plugin
只是我從來不會去用它而已

事實上在Linux的console也一樣有一套叫nano的套件可以做編輯器
只是我沒在使用它

當然
現在來講
大概1000個台灣的RD裏面會直接使用Linux OS的恐怕也只有零星幾個而已
多數還是在微軟的環境下工作
也因此造成了90後的RD大概也只會認為有大小寫混用的函式命名是正常的

其實並不是全域變數會去加底線
那並非真正的原因

在早期1990年代以前的C編譯器
都會自動把變數與函式名的前方加上底線
當時的C編譯器是分成兩次編譯的
第一次是將C程式碼轉譯成組合語言
而包括main在內都會自動轉譯成_main
所有的函式都會在前方增加一個底線字元
所以並非是scope的考量

就以純正的C語言的寫法來講
#define所定義的
原本一開始也是全部都用小寫
而是到了C++出現以後
微軟大量使用大小寫混用的變數名及函數名甚至於型態名稱
因此到了2000年左右的時候
大量出現了許多常用的#define採取全大寫字元的方式命名
因此就成了現在約定俗成的一種習慣
只要看到全部字元都是大寫就可以知道它是來自於#define所定義出來的
至於加上底線
對於C來講
原本一開始只有暫存器標籤在使用
比如x86上的AX 會寫成_AX或_ax
原本一開始的用途只是如此
事實上直到現在為止
Linux kernel 的標頭檔也從來並沒有主張過將全域變數以底線作為開頭

至於C++
我記得好像也沒有如你所說的約定習慣

可能只是某些派系的人在使用而已

至於Linux的環境上
會在前面加雙底線的倒是有看過
主要就是被封裝在struct內的一些變數
像是linux dvb 相關的標頭檔就有


...<div class='locked'><em>瀏覽完整內容,請先 <a href='member.php?mod=register'>註冊</a> 或 <a href='javascript:;' onclick="lsSubmit()">登入會員</a></em></div><div></div>

dh3014 發表於 2016-1-6 02:13 AM

chevylin0802 發表於 2016-1-5 09:49 AM static/image/common/back.gif
許多RD其實近幾年常常都把螢幕豎起來看程式碼
就是因為螢幕行數限制的問題
我還有看過有些人的程式


很多地方怪怪的感覺

在早期1990年代以前的C編譯器
都會自動把變數與函式名的前方加上底線
當時的C編譯器是分成兩次編譯的
第一次是將C程式碼轉譯成組合語言

我不認為編譯器普遍會有這種特性,頂多是部份編譯器的行為,要知道編譯之後連函式名稱都根本不一定需要留存在編譯後的組語程式裡了怎麼會有都必加上prefix _的道理。

「當時分兩次編譯」這種說法也怪怪的,即便是現行的c compiler也都是先將c code轉成asm,將asm翻成machine code一般稱做組譯。真要說多次翻譯的話現行compiler一般擁有比過去更多的中介層(像gcc/llvm都有各自的intermediate form)

就以純正的C語言的寫法來講
#define所定義的
原本一開始也是全部都用小寫

不太對吧,從一開始c的convention中,要用#define來實現「常數」功能的話都是用全大寫了,limits.h裡的INT_MAX還有NULL/EOF都是顯見的例子。當然用#define來實現macro的話則還是小寫沒錯。

至於大小寫混用的風格,不可否認微軟扮演了一個很大的推廣角色,但說是「C++出現以後」也不太對,這種風格是源自於pascal,pascal都比c還早出現了呢。而且現代的language也依舊是兩派並存,不明怎會有新人工程師都只用camelCase convention的說法,像python這個當紅的語言也是採用跟c語言一樣的end_of_file命名方式。...<div class='locked'><em>瀏覽完整內容,請先 <a href='member.php?mod=register'>註冊</a> 或 <a href='javascript:;' onclick="lsSubmit()">登入會員</a></em></div>

chevylin0802 發表於 2016-1-6 09:20 AM

本帖最後由 chevylin0802 於 2016-1-6 09:57 AM 編輯

dh3014 發表於 2016-1-6 02:13 AM static/image/common/back.gif
很多地方怪怪的感覺



這是因為你沒有經歷過那個年代
1985年第一次拿到Microsoft C Compiler 的時候
就是用這樣的方式產生組合語言的暫存檔
1986年Turbo C ver 1.0 剛出來就有IDE介面
雖然是Console下的IDE
也一樣是採取這個方式產生出組合語言的暫存檔

原因是C Compiler在早期的時候
都是分兩次編譯的
從C編譯成asm
再把asm進行組譯

同時當時不管是Microsoft C Compiler或者是Turbo C Compiler
都一樣可以使用 -S 的命令產生出 .asm 的組合語言檔

以前為了學組合語言
當時我都常常會把C用MSC跟Turbo C 編成.asm再去看組合語言碼
因此非常清楚
當初關於for迴圈的編譯方式
MSC與Turbo C採取不同的做法
MSC採取的是直觀的編譯
最後再用cmp + jg或jle等指令
而Turbo C則是用loop指令

尤其是你們一般都以為main()就是程式起始點
但事實上在那個時代的編譯器都還會在連結階段把程式起始碼連結進去
真正的程式起始碼其實跟寫組合語言時一樣
都是從proc start開始的
而那段proc start就會針對exe檔進行必要的設置
到完成設置之後才去執行call _main 這行組合語言指令

另外
#define , #if, #else #elif #end ......等所有這些被大家所熟知的語法
事實上在1985年以前的C語言是完全不存在的
Microsoft C Compiler V1.0時期完全不認識這些東西
而Turbo C V1.0 時也一樣不認識這些關鍵字語法
那些東西都是一直到了Turbo C V2.0的時候才開始有
在那之前
根本就沒有以#開頭的關鍵字與語法

至於最佳化編譯
Turbo C V1.0就已經有了

而這堆歷史
其實你們現在的人沒有遇過
LLVM也是到了21世紀才有的東西
在那之前根本就沒有這些理論與實作

至於#define後面的東西採取大寫還是小寫
也一樣並不是屬於C語言的規範
IEEE也從來不會去管或者規定C語言要如何寫作

至於C的寫法
凡是採取大小寫混用的函式名稱或變數名稱
都是源自於微軟的做法
正統玩UNIX like起家的人從來不用大寫命名

早期的PASCAL並沒有大小寫混用的習慣
不要胡亂講
本人第一隻PASCAL是在1982年的時候寫的, CP/M OS下的Turbo Pascal (z80 CPU)
當時的PASCAL Compiler 直接就是一步到位的編譯成機器碼
而且當時的PASCAL 是不能連結外部函式庫的
所有的PASCAL標準函式一律都只存在於編譯器裏
Pascal只允許小寫的方式命名
Fortran跟Forth 則是需要將部份關鍵字寫成大寫
至於當時最早期的BASIC則是完全都用大寫
跟現在你們從微軟系統上所學到的東西根本就完全不同
甚至於在C之前
void function這個名詞根本就不存在
void function在PASCAL的觀念是叫做procedure
至於有帶傳回值的function則是叫做function
而且這兩個都是PASCAL的關鍵字
至於Fortran則是分成function跟subrutine
subrutine就相當於void function

但不管怎麼說
當初C會竄起直接取代PASCAL的地位
最主要的功勞是在於pointer的觀念
當時古早時期的其它類高階程式語言無法使用指標或記憶體位址(除了組合語言可以以外)
甚至到了Turbo C 的時候還可以直接在C的程式裏指定x86 CPU的暫存器
從那個時候開始加速了C語言的普及化

1984年我第一次進學校計算機中心寫的第一隻Fortran程式時
當時的VAX系統還只能用ed而已
vi在那個年代裏還不存在

...<div class='locked'><em>瀏覽完整內容,請先 <a href='member.php?mod=register'>註冊</a> 或 <a href='javascript:;' onclick="lsSubmit()">登入會員</a></em></div>

dh3014 發表於 2016-1-6 01:52 PM

chevylin0802 發表於 2016-1-6 09:20 AM static/image/common/back.gif
這是因為你沒有經歷過那個年代
1985年第一次拿到Microsoft C Compiler 的時候
就是用這樣的方式產生組合語 ...

確實我年齡相較你是淺得多 也沒有經歷過1990年以前的coding經驗,感謝您分享很多早時候的程式語言演進史。

本來這篇回覆也打了一長串反駁的文字,想想已經偏離主題太遠索性刪光光希望回歸標題。

所以只針對較符合本串主題的地方繼續回覆,其他跟原串不相關的就自動忽略。

#define後面的東西採取大寫還是小寫
也一樣並不是屬於C語言的規範
IEEE也從來不會去管或者規定C語言要如何寫作

不錯這些東西不在語言spec的範圍內,但的確是屬於該語言的naming convention,小吐槽最早的c spec來自ANSI,後來以ISO為主,IEEE大概只有規範posix相關的函式庫。

不過既然「只」是naming convention,也會有很多東西漸漸不那麼合時宜被拿出來討論數次(比方說ALL_UPPERCASE_CONSTANT就是一個逐漸被廢棄的習慣)。

回到coding style與convention的地方,其實在我看來只要一個專案能統一一個style與convention就好,或許可以多尊重一些專案所使用語言的慣例,但也不是絕對, 而且style與convention其實對code的可讀/維護性相關度其實很低。...<div class='locked'><em>瀏覽完整內容,請先 <a href='member.php?mod=register'>註冊</a> 或 <a href='javascript:;' onclick="lsSubmit()">登入會員</a></em></div>

chevylin0802 發表於 2016-1-6 02:19 PM

本帖最後由 chevylin0802 於 2016-1-6 02:31 PM 編輯

dh3014 發表於 2016-1-6 01:52 PM static/image/common/back.gif
確實我年齡相較你是淺得多 也沒有經歷過1990年以前的coding經驗,感謝您分享很多早時候的程式語言演進史 ...
所有寫標準C語言的人都知道
C是K&R提出的
不是ANSI
用不著拿ANSI亂壓人

本人玩的是K&R style的C

從來不玩C99 style

至於UPPER_CASE_CONSTANT事情
直到目前為止
Linux kernel source一直都採行這種用法
從來沒變動過

我真懷疑有人根本就不曾研究甚至開發過Linux drvier
才會一直誤以為UPPER_CASE_CONSTANT被廢棄
更何況
UPPER_CASE_CONSTANT運用最廣泛還是JAVA
隨便去找任何一個JAVA API的文件
都可以找得到所有JAVA的public CONSTANT都是用UPPER_CASE
尤其我相信有不少人還在寫Android APK
不妨問一下他們的JAVA constant是不是都採用UPPER CASE

...<div class='locked'><em>瀏覽完整內容,請先 <a href='member.php?mod=register'>註冊</a> 或 <a href='javascript:;' onclick="lsSubmit()">登入會員</a></em></div><br><br><br><br><br><div></div>

tim12332000 發表於 2016-1-6 09:34 PM

小弟是發問者
經過一年多 出社會的經驗後 的心得小分享~{:39:}
發現 物件導向 的封裝 可以讓程式碼變得簡潔
盡量不要用到繼承 如果真的需要 繼承的層數能越少越好 (最好是沒有)
private public 的介面開清楚 有調理
在使用類別時 public 的介面夠靈活
使用物件的人 可以省去看內部實做的機會
達到封裝的效果

另外 MVC MVVM 遵守這些架構可以讓
複雜事情變簡單 鬆綁各個階層的耦合性~



...<div class='locked'><em>瀏覽完整內容,請先 <a href='member.php?mod=register'>註冊</a> 或 <a href='javascript:;' onclick="lsSubmit()">登入會員</a></em></div>

chevylin0802 發表於 2016-1-7 08:43 AM

本帖最後由 chevylin0802 於 2016-1-7 09:15 AM 編輯

tim12332000 發表於 2016-1-6 09:34 PM static/image/common/back.gif
小弟是發問者
經過一年多 出社會的經驗後 的心得小分享~
發現 物件導向 的封裝 可以讓程式碼變得簡潔 ...
繼承層數越少越好只是讓你許多事情單純化些
可是如果都這樣子做
那物件導向的最主要的用處就沒有了
如果連層數都完全沒有
那跟用struct還會有兩樣嗎?
struct 一樣能做

事實上只寫C的人就是靠struct在做物件的

C++只是讓你寫程式變得比C簡單
可讀性比C高許多
擴充的彈性也比C高許多
但是也很容易讓許多工程師找不到memory leak方面的bug

雖然如此
不過對於我這種在Linux平台下工作的人來講
是絕對不會去寫C++的程式
並不是我對物件導向有什麼意見
而是只要換人一接手
就常常又需要幫他們擦屁股除錯追蹤memory leak的bug
畢竟不管是JAVA, PHP還是Python
我仍然還是採取相當大的比例採用物件化
JAVA不需要擔心memory leak方面的問題
VM會自動回收
PHP跟Python完全都是靠直譯器執行的
也不會遇到memory leak的問題

不過不管怎麼說
我還是只會講{....}這種老問題
{....}裏面的層數與行數越少越好
不管是for() 的{....} 還是if()的 { ....} 還是while() {......}
switch case裏的case理面的敘述也是盡量少一點
有許多可以用#define來做或者用inline function來做
也有許多可以以把{.....}的部份內容拉出來獨立做一個函式
有點像是把它改寫成大綱之後再用另一個子章節去描述的觀念
這樣子比較容易閱讀
也比較不會出錯

另外就是不能只有直觀的思考
尤其很多人寫的if裏包的一堆判別式
都從來沒有個別去思考過每一種判別狀況
當if裏面包了五六種不同的判別式再進行and/or的邏輯之後
常常都會出現許多無形當中產生出來的bug
舉例來說
a and b and c and d
要a , b, c , d 都是true的時候才成立
那麼如果其中有三個是true一個是false的狀況時呢?
如果其中有兩個是true兩個是false的狀況時呢?
我看過許多人常常都只管if成立時的情況要處理什麼事
卻往往漏失掉更多else裏面應該做的事情
if(a && b) {
} else {
  if(!a) {.....}
  if(!b) {......}
}
是一種常用的方式
缺點就是在於
如果a 跟b 都是false的時候
就漏了判斷
於是就遇到一些人bug解不出來

一般來講
寧可用笨一點的方法做
if(a) {
if(b) {.......} else {.......}
} else {
if(b) {.......} else {.......}
}

當然也有可能有很多是不需要去顧及所有情況
但是要做到完全精簡又正確的程式寫法
那除非你有相當強的邏輯思考能力
而且又相當熟悉電子電路邏輯學所講的簡化方法
尤其是卡諾圖
不過我不認為程式設計師有多少人曾經認真去學過

...<div class='locked'><em>瀏覽完整內容,請先 <a href='member.php?mod=register'>註冊</a> 或 <a href='javascript:;' onclick="lsSubmit()">登入會員</a></em></div>

tim12332000 發表於 2016-1-9 04:13 PM

chevylin0802 發表於 2016-1-7 08:43 AM static/image/common/back.gif
繼承層數越少越好只是讓你許多事情單純化些
可是如果都這樣子做
那物件導向的最主要的用處就沒有了


小弟受教了
下麵我有點看不大懂~
-----
也有許多可以以把{.....}的部份內容拉出來獨立做一個函式
有點像是把它改寫成大綱之後再用另一個子章節去描述的觀念
-----
這邊是說像

function 買青菜()
...
end

function 買蛋()
...
end

function 買豬肉()
...
end

function 買菜()
    買高麗菜()
    買豬肉()
    買蛋()
end

這種想法嗎 ?


...<div class='locked'><em>瀏覽完整內容,請先 <a href='member.php?mod=register'>註冊</a> 或 <a href='javascript:;' onclick="lsSubmit()">登入會員</a></em></div>

chevylin0802 發表於 2016-1-9 04:31 PM

對的
事實上現在的cpu在做近程呼叫時
所需消耗的cpu resource
已經可以不用考量了

chevylin0802 發表於 2016-1-9 04:34 PM

本帖最後由 chevylin0802 於 2016-1-9 04:39 PM 編輯

chevylin0802 發表於 2016-1-9 04:31 PM
對的
事實上現在的cpu在做近程呼叫時
所需消耗的cpu resource


對的
概念是那樣子
有一些小函式可以用macro寫
有一些可以用inline function
好處則遠比在程式裡寫一堆註解更容易懂

不管是macro還是inline
都不會是call...ret的方式
並不會有呼叫函式時的堆疊資源消耗問題
...<div class='locked'><em>瀏覽完整內容,請先 <a href='member.php?mod=register'>註冊</a> 或 <a href='javascript:;' onclick="lsSubmit()">登入會員</a></em></div><br><br><br><br><br><div></div>
頁: 1 2 [3]