iT邦幫忙

2019 iT 邦幫忙鐵人賽

DAY 17
1

良好程式碼的優點大同小異。
不好的程式碼的糙點卻各有巧妙之處。

此篇的 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 的前置處理器

下面這一段的問題,其實不全然是前置處理器的問題,主要的原因在於,它有 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");\
  }\
}

使用

使用上,有幾個問題

  1. 需要分號結尾嗎?
  2. 變數丟進去,需要宣告嗎?

用起來的行為,已經違反原本 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

在六年前,SASS 的出現有如神乎其技一般,可以讓 CSS 的撰寫更加的靈活與方便 (沒錯這就是前置處理器處理的痛點)

SASS 提供的幾個特色,在寫 CSS 時有如神助。

  • 變數宣告
  • 巢狀式語法
  • 可重複使用的 mixin, extend 宣告
  • 數字的四則運算

當然不可或缺的還有程式語言常見的語法

  • @function
  • @return
  • @if
  • @while, @for

有時,也會遇到濫用的情形。

不建議

使用太過複雜的 @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 串接


上一篇
如何寫高品質 function (內聚性篇)
下一篇
技術債是糙 code ?? (上)
系列文
可不可以不要寫糙 code30

尚未有邦友留言

立即登入留言