再談C語言的模塊化設(shè)計(jì)
現(xiàn)代語言為了可以接近玩樂高積木的那樣直接組合現(xiàn)有的模塊,都對(duì)模塊化做了語言級(jí)別上的支持。我想這一點(diǎn)在軟件工程界也是逐步認(rèn)識(shí)到的。C 語言實(shí)在是太老了。而它的晚輩 Go 就提供了 import 和 package 兩個(gè)新的關(guān)鍵字。
這也是我最為認(rèn)可的方式。之前提到的方案只能說是對(duì)其拙劣的模擬。確認(rèn)語言級(jí)的支持,恐怕也只能做到這一步了。
在項(xiàng)目實(shí)踐中,那個(gè) USING 的方案我用了許多年,還算滿意。之前有過更為復(fù)雜“精巧”的方法,都被淘汰掉了。為什么?因?yàn)槊棵恳胄碌母拍?,都增加了新成員的學(xué)習(xí)成本。因?yàn)閹缀趺總€(gè)人都有 C 語言經(jīng)驗(yàn),但每個(gè)人的項(xiàng)目背景卻不同。接受新東西是有成本的。任何不是語言層面上的“必須”,都有值得商榷的地方。總有細(xì)節(jié)遭到質(zhì)疑。為什么不這樣,或許會(huì)更好?這是每個(gè)程序員說出或埋在心里的問題。
那個(gè) USING 的方案遠(yuǎn)不完美,它只是足夠簡(jiǎn)潔,可以讓程序員勉強(qiáng)接受而已。但其實(shí)還不夠簡(jiǎn)潔。因?yàn)閺倪壿嫳磉_(dá)上來說,它是多余的。一個(gè)模塊使用了另一個(gè)模塊,代碼上已經(jīng)是自明的。從 C 語言的慣例上來說,只要 #include 了一個(gè)相關(guān)的 .h 文件,就證明它需要使用關(guān)聯(lián)的模塊。
光用宏的技巧很難只依靠一次 #include 就搞定正確的模塊初始化次序。因?yàn)?C 語言并沒有明顯的模塊概念。如果將每個(gè)子模塊都編譯為動(dòng)態(tài)庫可能能一定的解決問題(我曾經(jīng)試過這種方案),但卻會(huì)引出別的問題。細(xì)粒度的動(dòng)態(tài)庫局限性太大。
這兩天我結(jié)合這半年學(xué)習(xí) Go 語言的體驗(yàn),又仔細(xì)考慮了一下這個(gè)問題。想到另一個(gè)解決方案。
如果我們能規(guī)范系統(tǒng)中子模塊 API 的命名規(guī)范,或許可以借助編譯器和相關(guān)工具來做一些 meta programming 的工作。
我們可以使用 objdump 來分析編譯好的 .o 文件。比如有一個(gè)模塊 foo ,實(shí)現(xiàn)在 foo.c 中。objdump -t 可以得到 .o 中引用以及導(dǎo)出的符號(hào)。
我們要求所有子模塊中的 C API 都遵守一致的命名規(guī)范,假設(shè)用馱峰命名的話,foo 模塊中的 Api 就看起來像這樣 fooApi 。objdump 的結(jié)果可以輕易的識(shí)別出規(guī)范內(nèi)的引用的其它子模塊有哪些。然后生成一個(gè)類似之前提到的 USING 方法可以調(diào)用的初始化函數(shù)。自定義的模塊初始化函數(shù)可以統(tǒng)一命名為 fooInit 的形式,當(dāng)這個(gè)初始化函數(shù)存在,則由自動(dòng)生成的代碼調(diào)用一下即可。
整個(gè)過程可能比較繁雜,但很容易用 make 這樣的構(gòu)建工具自動(dòng)化進(jìn)行。具體實(shí)現(xiàn)我就不列出了?;蛟S不久會(huì)新開開源項(xiàng)目實(shí)踐一下。
原文鏈接:http://blog.codingnow.com/2011/04/module_initialization.html#more
【編輯推薦】