寫在前面
我要寫得都是一些牢騷滿腹的東西。包含了我十年來覺得很不爽的東西,也有一些我覺得很不錯(cuò)的觀點(diǎn)响疚。寫代碼是個(gè)辛苦活,維護(hù)代碼更辛苦瞪醋。有些時(shí)候?yàn)榱粟s時(shí)間會寫出低質(zhì)量的代碼忿晕,然后花大量的時(shí)間去還債。但是有些時(shí)候低質(zhì)量的代碼并不是由于時(shí)間的原因造成的银受。
客觀來講践盼,低質(zhì)量的代碼可能是由于程序員對需求的了解不夠深入而引起的。大部分的軟件都是應(yīng)用于各行各業(yè)的宾巍,而程序員大部分又都是CS專業(yè)畢業(yè)的咕幻。拿醫(yī)療行業(yè)舉個(gè)例子,寫代碼的人和最終的使用者之間的差距顶霞,用“鴻溝”來形容都不為過肄程。而且這種“鴻溝”基本上沒有彌補(bǔ)的可能性。程序員改了無數(shù)的BUG选浑,可能都不知道軟件是在什么樣的環(huán)境中被什么樣的人所使用的蓝厌。在沒有充分了解需求的情況下寫出高質(zhì)量代碼的可能性是有的——這需要一個(gè)能力非常強(qiáng),而且對于行業(yè)知識非常了解的人來帶領(lǐng)整個(gè)團(tuán)隊(duì)——古徒,但是這種可能性很低褂始。
主觀來講,在這個(gè)世界上壓根就沒有太多所謂的高質(zhì)量代碼描函。高質(zhì)量代碼的分布能遵循2/8原則就謝天謝地了崎苗。首先“高質(zhì)量”這三個(gè)字的含義都很模糊——對于不同的行業(yè)狐粱,不同的時(shí)期,“高質(zhì)量”所指代的內(nèi)涵胆数,以及內(nèi)涵的優(yōu)先級都是不一樣的肌蜻。舉個(gè)例子,20世紀(jì)80年代的代碼必尼,大約是以運(yùn)行速度快和占用內(nèi)存小為“高質(zhì)量”的標(biāo)桿的蒋搜。而到了本世紀(jì),硬件資源不那么稀缺了判莉,在大部分的場景下豆挽,可讀性和可維護(hù)性要優(yōu)先于運(yùn)行速度和空間耗費(fèi)。即便對于同一個(gè)系統(tǒng)券盅,在不同的模塊間帮哈,代碼的要求也可能是不同的。對于DSP里運(yùn)行的代碼锰镀,首要因素是速度快和結(jié)構(gòu)清晰娘侍,而上位機(jī)的代碼可讀性更重要。
啰嗦這么多只想說泳炉,寫好代碼是一件千難萬難的事情憾筏,是一個(gè)需要隨著時(shí)間而累積和修行的事情。只有扎實(shí)地做好每一步花鹅,才能寫出“高質(zhì)量”的代碼氧腰。
基本原則
十年開發(fā),大部分都是在修修補(bǔ)補(bǔ)刨肃,根本沒有機(jī)會打造屬于自己的開發(fā)環(huán)境和開發(fā)模式容贝。幾個(gè)月前從公司跳了出來,算是開始了一段創(chuàng)業(yè)歷程之景。這是我有生以來的第一次斤富。以前我對創(chuàng)業(yè)是不屑的,因?yàn)樵谖业挠^念里锻狗,好的技術(shù)全在大公司里满力,所以我前三年全部的經(jīng)歷都在琢磨如何提升自己,如何進(jìn)入大公司轻纪。在大公司里待了七年油额,修修補(bǔ)補(bǔ),挖坑排雷刻帚。這七年的提升不僅僅是技術(shù)上的提升潦嘶,更重要的是心智和領(lǐng)導(dǎo)力的提升。當(dāng)你的思維進(jìn)入了正軌崇众,大部分技術(shù)都只是時(shí)間而已掂僵。當(dāng)你見識得夠多航厚,所有的眼花瞭亂其實(shí)都是萬變不離其蹤。我不能說所有的東西都是一樣的锰蓬,但是解決問題的思路也就是那么有限的幾種幔睬。以前沒有實(shí)現(xiàn),并不是前人不夠聰明芹扭,而是現(xiàn)實(shí)世界的約束太強(qiáng)——巧婦難為無米之炊麻顶。因此,作為開發(fā)者舱卡,最理智的作法是從現(xiàn)有的技術(shù)里面辅肾,挑出我認(rèn)為最好的東西(有時(shí)候是隨機(jī)碰到的),組合使用它們轮锥,實(shí)現(xiàn)我的系統(tǒng)矫钓。
舉個(gè)簡單的例子,我選擇在開發(fā)中用Conan進(jìn)行二進(jìn)制包管理交胚。其實(shí)這種類型的包管理工具一直都在我們身邊——Nuget就是一個(gè)很成熟的應(yīng)用份汗。但是我為什么不選擇用Nuget呢盈电?沒有什么特別的理由蝴簇。我之前對于Nuget的使用體驗(yàn)并不是特別好,無論是上傳還是下載都很費(fèi)勁匆帚。正好在推上看到了Conan的宣傳熬词,就下載來用用,發(fā)現(xiàn)不費(fèi)勁吸重,我要的功能它都提供了互拾,所以就開始用起了Conan。
這樣的選型方式在我前七年的工作環(huán)境里確實(shí)有些隨意嚎幸。大公司對于正式引入開發(fā)流程的任何工具和設(shè)備颜矿,都有嚴(yán)格的驗(yàn)證流程需要遵守。并且要做出若干的表格進(jìn)行對比分析嫉晶。這樣做有它的好處骑疆,但是對于創(chuàng)業(yè)公司未免就有點(diǎn)“殺雞焉用牛刀”的感覺。并且隨著整個(gè)業(yè)界水平的提升替废,同一種類型的工具在質(zhì)量上并不會有太大的差別箍铭。這也就是我敢于隨心選型的心理基礎(chǔ)。
總結(jié)一下我的基本原則只有兩點(diǎn):
- 適用于小型團(tuán)隊(duì)
- 看眼緣
開發(fā)語言的選擇
十年之間斷斷續(xù)續(xù)接觸了很多編程語言:C++, C#, Go, JS, Scalar,等椎镣。其中C++是我吃飯的家伙诈火,所以格外花得時(shí)間多一點(diǎn)。其它都是有需要的時(shí)候翻翻文檔状答,邊查邊寫冷守。幸運(yùn)的是刀崖,除了C++之外其它語言學(xué)起來都不是那么難。C++活了這么多年教沾,都快成活化石了蒲跨,雖然大家都覺得它很難搞,但是總還是有它的強(qiáng)項(xiàng)授翻。
首先就是性能問題或悲。我們所開發(fā)的系統(tǒng)是一個(gè)封閉的系統(tǒng),要給客提供軟件堪唐、硬件以及機(jī)械結(jié)構(gòu)的一整套東西巡语。并且,封閉式的系統(tǒng)一般情況下不會升級硬件淮菠,網(wǎng)絡(luò)資源也很有限男公。因此,相較于開放式的開發(fā)環(huán)境(類似于網(wǎng)頁應(yīng)用或者一些桌面應(yīng)用)合陵,封閉系統(tǒng)的計(jì)算資源受到很大的制約枢赔,性能就成了一個(gè)非常重要的考量方面。
其次與已有的成熟系統(tǒng)有關(guān)拥知。感謝開源社區(qū)的發(fā)展踏拜,以及全世界無數(shù)英才的無私貢獻(xiàn),現(xiàn)在已經(jīng)很少需要從無到有地開發(fā)一個(gè)項(xiàng)目了低剔。大部分基礎(chǔ)性的工作在社區(qū)里都能找到速梗。作為開發(fā)者所要做的事情,就是找到你需要的項(xiàng)目襟齿,再把這些項(xiàng)目做裁剪姻锁,改進(jìn)并膠合起來。除了開源社區(qū)猜欺,你也總能找到一些商用軟件可以全部或者部分得解決你的問題位隶。這些開源或者商業(yè)的軟件所使用的語言,一定程度上會決定項(xiàng)目的開發(fā)語言選擇开皿。
這些原因綜合起來涧黄,讓我決定以C++作為主力的開發(fā)語言。
單元測試
十年C++的開發(fā)經(jīng)驗(yàn)中副瀑,寫單元測試的經(jīng)歷少之又少弓熏,也只在我自己的業(yè)務(wù)項(xiàng)目里面會寫寫測試用例。在公司正式的開發(fā)過程中糠睡,從來沒有寫過單元測試挽鞠。因?yàn)榇蠹叶己苊靼祝oC++寫單元測試就是一個(gè)坑,尤其是給一個(gè)已經(jīng)跑了20年信认,并且從來沒有重構(gòu)過的系統(tǒng)寫單元測試材义,這個(gè)坑不是一般得大。對于這種老系統(tǒng)嫁赏,寫單元測試的可能性已經(jīng)幾乎為零(需要重構(gòu)幾乎所有的業(yè)務(wù)代碼才有可能寫出較大覆蓋率的測試用例)其掂,更不要說市場部門還在不停地壓縮開發(fā)時(shí)間。另外潦蝇,這種老系統(tǒng)還有一個(gè)特點(diǎn)就是所有的開發(fā)人員被迫使用“open-close”原則款熬。對于這種老系統(tǒng)已經(jīng)沒有人能夠完全理解(比深度學(xué)習(xí)訓(xùn)練出來的網(wǎng)絡(luò)還要神秘),所以最保險(xiǎn)的方式就是加代碼攘乒。有些年輕人就是不信邪贤牛,但是碰過兩次壁之后就都學(xué)乖了,十年來從沒有過例外则酝。
所以在系統(tǒng)開發(fā)的起初就要求必須給每個(gè)組件寫單元測試和系統(tǒng)測試殉簸。單元測試是對源代碼的測試,而系統(tǒng)測試是對組件的測試(系統(tǒng)測試可以是對單個(gè)組件的測試沽讹,也可能是對多個(gè)組件組合功能的測試)般卑。在C++里組件一般是以shared library的形式出現(xiàn),也有可能是header only library爽雄。無論是哪種形式蝠检,原則上每一個(gè)體現(xiàn)具體功能的文件都應(yīng)該被測試到,即單元測試應(yīng)該直接將相關(guān)文件包含進(jìn)來盲链,針對里面的代碼寫測試用例蝇率,而不是針對組件暴露的接口寫測試(這屬于系統(tǒng)測試)迟杂。比如有一個(gè)名字叫Foo的庫刽沾,庫里面有4個(gè)文件分別是FooA.h/cpp和FooB.h/cpp。在做單元測試的時(shí)候首先需要建立測試工程TestFoo排拷,然后將Foo里的4個(gè)文件全部加到TestFoo工程里侧漓,然后分別針對4個(gè)文件里的代碼寫測試用例。然而實(shí)際上這樣的測試用例很難寫(有時(shí)候基本不可能监氢,有時(shí)候則是代價(jià)太大)布蔗。前段時(shí)候用Boost.Asio寫了串口通信的庫,跟串口相關(guān)的功能就很難寫測試浪腐。另外googletest(我在項(xiàng)目里使用google test)也沒有很好地支持異步調(diào)用纵揍。
對于沒有反射支持的語言,單元測試都會是個(gè)頭疼的問題议街。很多測試代碼寫起來非常繁瑣泽谨,我甚至一度懷疑寫單元測試是不是一個(gè)正確的選擇。尤其是在寫Fake Object的時(shí)候,想要模擬一些與硬件異步通信相關(guān)的行為吧雹,經(jīng)常會被搞得焦頭爛額骨杂。但是一但單元測試寫出來了,這些測試用例就會跟著你的項(xiàng)目一起成長雄卷。在寫單元測試的過程當(dāng)中你會更加了解你自己的代碼搓蚪,也更了解項(xiàng)目的需求,扎扎實(shí)實(shí)地寫出高質(zhì)量的代碼丁鹉。