良好程式碼的優點大同小異。
不好的程式碼的糙點卻各有巧妙之處。
此篇的 coding 建議,不適用於 C
C 語言做到 template 或 generics 就只能使用 MARCO 這樣的前置處理器。
而 C++ 語言本身內建 template 或 generics 的寫法,所以能使用語法取代就使用語法,不要依賴 C++ 相容於 C 的特性,狂用 MARCO。
前置處理器的原理,其實就只是語法的互相取代。不過也許未來的前置處理器會愈來愈好。
前置處理器,用在 function 上,要非常非常小心。
前置處理器的缺點,在於 debug 無法 trace code,無法下中斷點。
try-catch
在這使用前置處理器,不是一個好主意。
設計
#define START()\
try {\
#define END()\
}\
catch(exception& e) {\
log("Occur exception");\
return ;\
}\
catch(...) {\
log("Occur exception");\
return ;\
}\
實際使用
void function () {
START()
// do something...
END()
}
因為就是發生例外了,才要 trace code、下中斷點
如果真的要 trace code、下中斷點 要怎麼做?
就做一次前置處理器的行為,把 code 貼過去,變成真正在跑的樣子,再讓錯誤發生,才可以 trace code、下中斷點。QQ
下面這一段的問題,其實不全然是前置處理器的問題,主要的原因在於,它有 side effect
設計
#define BEGIN_TRANSACTION(transaction, status_code)\
int status_code = ERROR_SUCCESS;\
Transaction* transaction = 0;\
try\
{\
transaction = new Transaction();\
try\
{\
#define END_TRANSACTION(transaction, status_code)\
}\
catch(...)\
{\
status_code = TRANSACTION_ERROR;\
log("END_TRANSACTION Error");\
}\
}\
catch(...)\
{\
}\
{\
try\
{\
if( !transaction->Commit() )\
{\
if( status_code == SUCCESS )\
{\
status_code = TRANSACTION_ERROR;\
log("END_TRANSACTION Error");\
transaction->Rollback();\
}\
}\
}\
catch(...)\
{\
status_code = TRANSACTION_ERROR;\
log("END_TRANSACTION Error");\
}\
\
try\
{\
delete transaction;\
}\
catch(...)\
{\
status_code = TRANSACTION_ERROR;\
log("END_TRANSACTION Error");\
}\
}
使用
使用上,有幾個問題
用起來的行為,已經違反原本 C/C++ 的使用常理,就不是一個好的設計。
但是若使用一般 function 的設計,它所帶來的限制,反而會帶你走到一個相對好的設計。
void doSomething() {
BEGIN_TRANSACTION(transaction, status_code)
//do something...
END_TRANSACTION(transaction, status_code)
}
上述程式碼還有 try-catch
可以說嘴,就讓我留到下次吧。
我自己是不使用 前置處理器那一派的,所以沒有什麼使用上的好方法,最好的方法就是用語法取代它。
不過,要推薦一下 jserv 的「你所不知道的 C 語言: 前置處理器應用篇」
使用前置處理器隱藏複雜性、抽象了完整的程式碼片段並且有適當的命名取代之
在 C++ 可以用在「條件編譯」[1]
可以在每一段不需要到產品 code 上的程式碼加上
#ifdef _debug
/* debug code*/
#endif
也可以寫成一段 MARCO function。
#if defined(DEBUG)
#define DebugCode(code_fragment) { /* debug code*/ }
#else
#define DebugCode(code_fragment)
#endif
在六年前,SASS 的出現有如神乎其技一般,可以讓 CSS 的撰寫更加的靈活與方便 (沒錯這就是前置處理器處理的痛點)
SASS 提供的幾個特色,在寫 CSS 時有如神助。
當然不可或缺的還有程式語言常見的語法
有時,也會遇到濫用的情形。
使用太過複雜的 @function
來產生 CSS
CSS 語法本身提供的耦合性,來自於 Selector 的共用,如此而已。如果提供了額外的耦合,會與 CSS 語法的使用習慣相差太遠,而造成可讀性大幅降低。
將所有的 變數、 mixin 或 extend 放在一起
不要依語法放置,要依照宣告與使用編排,依照語意放置
混用 calc 和 SASS 四則運算
其實語法上會超複雜!!
考慮生成檔案大小來編排語法
在 SASS 中若你這樣寫,確實會生成較少重複的 @media desktop-size
語法,但是這也是前置處理器的問題,並不是開發者的問題。
mobile.sass
.card {
// all design of mobile
width: 100%;
}
desktop.sass
@media desktop-size {
// all design of desktop
.card {
width: 25%;
}
}
我個人建議的做法
檔名是以畫面或元件區分,而每個畫面或元件若有屬性設計成不同尺寸可以同時出現。
原因是,在處理 RWD 時,其實會一起考慮「某個畫面的各個尺寸」,而不是「某個尺寸的各個畫面」。
index.sass
// all design of mobile
.card {
width: 100%;
@media desktop-size {
// all design of desktop
width: 25%;
}
}
過度使用巢狀式語法
巢狀式語法,很好用,好用到常聽見「就像用 HTML 一樣呀」。
所以,有看過這樣的用法,這其實是不良的用法
SASS
.folks {
.talkbubble {
@include set_font_color($light_gray);
position: relative;
line-height: 40px;
//...
&::before {
@include aligh_center($triangle_hdight);
@include aligh_sharp($triangle_hdight);
}
}
//...
}
HTML
<div class="folks">
<div class="talkbubble text-center my-5">We design greate websites and all sorts of interactive products from start to finish</div>
<!-- ... -->
</div>
在這個例子中, talkbubble
是一個可以獨立存在的描述,而不一定是非在 folks
不可。
這個判斷,也是回歸到問題本身,判斷語法與語意的咬合問題。
過度使用巢狀式語法 更極端的例子
將 Selector 變成超級長的情況。
這將會降低瀏覽器讀取 Selector 的效能。[2]
body {
header {
nav {
//...
}
}
main {
//...
}
footer {
//...
}
}
或者
body {
header {
nav {
//...
}
}
}
body {
main {
//...
}
}
body {
footer {
//...
}
}
[1]: 《Code Complete 2/e》, Ch8 防禦性程式設計, 8.6 除錯輔助工具
[2]: 前端新手村 CSS Cascade 串接