contribute by <yenWu
>
看完兩個中文連結,就我理解上,所謂的 refactor的目的就是讓程式更易於維護、新增功能、容易找出bug出在那邊,將程式的分工並將功能最小化增加 reuse,而他是一個在寫程式時就該思考該改善的步驟,如和將程是的分工變得非常明確,不讓重複的code一直重寫。
《Refactoring》書中提到22個壞味道,包含:duplicated code、long method、large class、long parameter list、divergent change、shotgun surgery、feature envy、data clumps、primitive obsession、switch statements、parallel inheritance hierarchies、lazy class、speculative generality、temporary field、message chains、middle man、inappropriate intimacy、alternative classes with different interfaces、incomplete library class、data class、refused bequest、comments。
參考:對於重構的兩則常見誤解
解決方式:這個部份其實我參考了很多命名的文件,像是 Camel case 的方式(ex: myName),或是匈牙利命名法,我最終選擇 Camel case(可是老師給我的 comment是 linux kernel 不使用這些…),目前我已經有定立自己的 coding style。
解決方式:在每一個流程前給一個註解,應該說在寫 code 時就應該要建立這樣子的流程圖,而不是寫完之後再去加上。
解決方式:就以 append()這裏麵的很多變數可以不用的,像是 entryStart,搞不好連 nthread也可以省略,因為有 Macro 在。
解決方式:說到 code reuse,其時我應該把大部分的 code 丟到phonebook opt裡,這樣會讓 main看起來簡單易懂,而且也能增進 reuse。
解決方式: Get number of CPUs in Linux using C jserv
水啦,這個好神啊Yen-Kuan Wu
sysconf(3)
主要回傳數字,透過不同的 flag 能得到不同的回傳值,算是挺全面的。
我用它來改我的 hardcode THREAD_NUM
#define THREAD_NUM (int)(sysconf(_SC_NPROCESS_ONLN))
我也有練習其他的部分,這邊就不列舉了。
https://github.com/yenWu/PracticeMakePerfect/commit/6e3ae2b25a75c6ad11d556838c4a75e7ab738b87
上面的版本會有個問題是,重複 call這個 function,所以最終版本是這個
const int thread_num = (int)sysconf(_SC_NPROCESSORS_ONLN);
#define THREAD_NUM thread_num
將計算細項時間,替換到 TIMING
make TIMING=1
說到 refactor的改善,我嘗試想把我的 code做物件化,我覺得他給我們最大的好處就是,讓別人一眼就看出來我們的 phonebook的 attribute 和 可以使用的 method,而不會是像 append這樣簡易的命名,讓我們不清楚他到底再 append哪些東西,而且要加額外的功能只要再多開一個 function pointer,大家一目了然phonebook 多了什麼功能。
typedef struct _phonebook {
entry *pHead;
entry *pLast;
unsigned long long count;
entry *(*append)(char *lastName, entry *pHead);
entry *(*find)(char *lastName, entry *pHead);
} phonebook;
請留意縮排風格,用 4 個空白而非 tab。我們期望寫作
entry *pHead
而非entry* pHead
,operator*
放的位置很重要 jserv
好的,這我真的沒注意到,囧。Yen-Kuan
這個部份我會留在後面再改動,這其時會改到全部程式碼,因為我希望把 list都保護在phonebook裡。
想到 code reuse,我目前在做一份比較完整版的 linkList,考慮到了如果我們今天突然 linkList裡的變數不在是 int data的話,我要怎樣改動最少的 code呢?
code: W2
typedef struct _context{
int data;
} context;
typedef context *ctxPtr;
typedef struct _listNode {
ctxPtr ctx;
struct _listNode *next;
} listNode;
將 new 跟 init 分離。
static ctxPtr initCtx()
{
ctxPtr p = (ctxPtr) malloc(sizeof( context));///FIXME:Dup
assert(p && "malloc error");
return p;
}
static ctxPtr newCtx(int data)
{
ctxPtr p = initCtx();
p->data = data;
return p;
}
這邊我又想到了,這個 malloc也是一直一直再寫,assert也要一直寫,所以我有沒有辦法把他包成一個 function呢?這的感覺就像是在寫 system 時要常常注意 open有沒有開成功,一堆的。
回到原點,我該怎樣讓一個 malloc 支援很多 type勒, template
這邊我想了一陣子,想到最初的版本就是 Macro,用他來替換字元是最好的方式
#define malloc(type, ctxtype, num) \
type malloc(sizeof(ctxtype) * num)
但後來覺得 Macro是直接替換,所以真的比較難 debug,所以我詢問老師後,老師建議我使用 C11 的 _Generic,大概用法如下,我也寫過一個範例如下,這感覺出乎意料的好用
#define printf_dec_format(x) _Generic((x), \
char: "%c", \
signed char: "%hhd", \
unsigned char: "%hhu", \
signed short: "%hd", \
unsigned short: "%hd", \
signed int: "%d", \
unsigned int: "%u", \
unsigned long int: "%ld", \
long long int: "%lld", \
unsigned long long int: "%llu", \
float: "%f", \
double: "%lf", \
long double: "%Lf", \
char *: "%s", \
void *: "%p")
#define print(x) printf( printf_dec_format(x), x)
#define printnl(x) printf( printf_dec_format(x), x); printf("\n")
int main(int argc, char *argv)
{
printnl('a');
printnl((char) 'a');
printnl(123);
printnl(1.234);
}
請修正 coding style! 不要亂加空白,「
(
之後」和「)
之前」都不該有額外的空白。重申: 4 個 space,而非 tab,程式開發很在意紀律和規範。 jserv
慘了= =,我可以請問老師一下是直接敲4個space 還是說astyle 或是改 vimrc。Yen-Kuan Wu
直接在 .vimrc 改比較方便,把 tab 轉換成 spaces。
set expandtab " 空格取代 tab
set tabstop=4 " 4 個空格
set shiftwidth=4 " 針對縮排所需要的空格個數 Charles Lee
Thanks. 我也是這麼想的,感謝分享,我這就去改.vimrcYen-Kuan Wu
參考 Mathias Brossard 的 mbrossard/threadpool (BSD Licens)
他這份實作有完整程式碼,行數不是很多,但他開出來的 api跟 struct member都非常清楚,不像我的 append_a
完全不知道是什麼。
這邊我看到他的 thread
用的 struct
很漂亮,也是我要改善再我實作裏面的。
typedef struct {
void (*function)(void *);
void *argument;
} threadpool_task_t;
目前想要嘗試將其改寫成 ring buffer
他已經是…Yen-Kuan Wu
首先,我嘗試跟著 mbrossard的腳步重建一個 threadpool,先整理出我的初步構想。
遇到一個神奇的 bug,invalid storage class for function.
上網查的結果都說我少加 {},我就直接 -E
去看發現沒有,再來我看 stackoverflow,他說我把 static function 宣告在 header,所以我就把他宣告再 .c裡,這讓我想到該如何實作一個 private 的 OOP,我們可以用 static 的特性將部份東西鎖在 .c檔裡,讓後 header file 當作 API 開出去。
我參考我 IPC的書籍,嘗試選寫一份 condition variable 的用法。
do{ } while (0)
常會看到這寫法,特別在 Linux 核心原始程式碼,在老師修改我的debug.h也出現,目前我看到的兩個用法是第一個
主要參考工程師的好朋友:http://stackoverflow.com/questions/257418/do-while-0-what-is-it-good-for
FIX: warning: ISO C90 forbids mixed declarations and code [-Wpedantic]
再剛載下來時,compile時發現一些 warning,很像是 C90有特別規定 declaration有需要放在前面,我就順手改一下
但是 push 上去有個東西很神奇網站叫 travis-ci,他很像是個可以把你的 code 拿去 linux 和 mac 上,而且使用不同的 gcc 版本編譯,而我 pull 上去的版本有一沒過,Mac gcc-5
,其實我有去看裡面的編譯過程
https://travis-ci.org/mbrossard/threadpool/jobs/164642802
$ make
gcc-5 -D_REENTRANT -Wall -pedantic -Isrc -c -o tests/thrdtest.o tests/thrdtest.c
make: gcc-5: No such file or directory
make: *** [tests/thrdtest.o] Error 1
看起來不太像我的問題,反而比較像是他們 gcc-5 沒裝成功的感覺。
上游已在 Oct 4 修正了 jserv
看到老師的提點git rebase -i
,我還在想說要怎麼樣讓他們重新測試哩。
yenWu
phonebook-concurrency
sysprog21