是否geek精神妨礙了編碼?
背景----------------------------下學期要開數據結構了,最近在嘗試寫stl。
目前還在寫vector,發現vector的設計沒那麼簡單。有很多關於性能方面的問題,比如insert操作時如果需要擴容,是直接調用reserve(個人認為方便,但是插入pos後半段元素又要重新複製),還是直接開闢新內存遍歷複製(遇到pos時特殊對待,但是感覺編碼又不方便,要寫if什麼的)。
問題----------------------------
不糾結本例的背景(如果能給出針對性的想法更好。。。)
是否各位大牛在寫代碼時都有這樣的困境:geek精神(想把性能等做到極致)妨礙了編碼(考慮太多而不知道該如何權衡)?
一句話:如何看待並解決這種困境?//------------08.27.21 問題更新
這幾天陸續看到各位答主的回答,受益頗深。
大家的觀點主要分為以下幾類:
(1)軟體工程角度:先把任務完成,再根據實際情況調整代碼邏輯,優化性能。當然過程中不能寫出太蠢的代碼。過早的優化是萬物之源!!!(這句看著很有感覺^-.-^)
(2)學習角度:只要學到了我所關心的東西了就好。造輪子的過程就應該多想,從而提高。
(3)其他:寫的太少,想太多。。。
題主從大家的回答中學到了很多東西,對今後的學習、開發很有幫助!(感謝)
回到本例:
(1)這不是O(1),不同選擇下,pos後的元素會複製兩次,我覺得是O(n)。
(2)當時吃完飯後很快就解決了問題,其實只是自己想多了而已,編碼()也沒複雜到哪去。
(3)我知道encoding和programing的區別,怪我用詞不準,不要介意。。。
(4)剛剛大致寫完了deque,中間調了幾個小時的bug。完成後跟標準庫對比,發現自己的push_back快很多,很興奮哈哈。其實是擴展中控map的策略不同,我每次擴展都把原map調到中間位置,保證不會出現一端過於臃腫的情況,減少了內存的分配。其他性能還沒測,估計會比stl差,因為自己iterator寫的很爛。
截圖(push_back1000w個int,stl::deque和moon::deque的耗時比較,最後的數字是map控制的緩衝區個數):@SodaSea Super 那句"願聞其詳"真的很贊!!!
歡迎補充!
沒有妨礙,但發揮Geek的時機很重要
先編寫一個夠用的版本,然後再回頭用更加優雅的方式改寫也為時不晚。反而一上來就追求最優的可能會困難許多,可能還會做許多無用功。
講個真實的故事吧:我認識一個朋友,他最早在自己寫一個仿Minecraft的遊戲(具體叫啥我就不說了免得被說打廣告),貼吧上人氣還挺高,帖子有幾萬回復。
後來過了一段時間他和幾個因為開發這個遊戲而認識的朋友一起開發,隨後開發基本上就轉移到了「怎麼優化」、「怎麼讓FPS提高20%」等等。很長的一段時間裡面在群眾眼裡遊戲基本上沒有太大的改變(雖然的確裡面改了很多速度也有提高)……
再後來呢?他們把遊戲推倒重寫了……結果是直到現在他們還在弄「怎麼架構能讓遊戲變得高效」、「怎麼架構能讓代碼更加優雅」,然而現在遊戲還沒有回到原來最初版的水平……(當然中間的確也積累了不少,造了一些輪子)
--------------------------------原答案--------------------------------
「過早的優化是萬惡之源」
我一開始給各位說早點把代碼寫出來比「優化」更重要的答主點了贊。然後抬頭看了一眼題主要做的事情(重寫STL),默默收回了這些贊……
主要是,重寫STL根本就不是一個工程。
從工程的角度,除非公司自己就是做編譯器的,否則不會有動力去重寫STL。基本上不會有說自己重寫一遍STL可以取得什麼工程上的好處的事情。即使有,也要跟「fork一份STL然後向上游交補丁」仔細權衡。
所以從一開始,你決定寫STL就說明了這根本就不是一個工程,而是一個(自己給自己準備的)作業/學習材料。而你前一句「下學期要開數據結構了」更證明了這一點。所以你的目的根本不是把STL寫出來,而是在寫STL的過程里學到東西。
既然是學習過程,我認為其他答主的評價都是不合適的,你自己只要學到了你關心的東西了就好,就算最後寫出一坨不可維護的玩意來也沒事,反正沒人逼你維護。數據結構課又不是軟體工程課。當然如果你能順便搞定軟體工程那更好。
當然有一些方法論我覺得對學習和工程是通用的:準備一個workload,對它調用(系統)STL的函數進行profile,知道它調用哪些函數耗時多。這樣,你自己寫STL的時候,就可以有的放矢,知道有很多函數不需要特別優化。(一個佔總時間0.05%的函數,你優化了10倍性能,整個程序也才快了0.045%)Geek 才不管性能呢,重點是要 Coooooooooool!
你要明白,除非你是為應用場景絕不變化的東西做design寫實現(比如基礎容器,演算法),否則是不存在「性能極致」可以去追求的。
在競爭密集型行業,業務需求的發展速度決定了系統的快速演化。任何對系統的一個snapshot的「極致優化」,都會變成下個版本「極致優化「的障礙(2—8原則,最後%20的性能往往需要額外%80的努力來獲得),甚至成為增加功能,增加系統可用性,增強企業競爭力的障礙。因為「極致優化」導致了你的設計和code對現在系統的snapshot的「過耦合」。
這也是為什麼waterfall開發流程和敏捷開發流程從源頭上不同的原因,也是傳統行業與互聯網行業畫風如此不同的原因。因為傳統行業中有大量的,上一代的習慣了waterfall流程的「技術專家」,他們總是抱怨需求的變化,和追求著各種各樣的「極致」。其實他們技術能力本不差,但是卻會做出一坨一坨的BBoM,就是因為不懂得:why and how to擁抱變化這個道理,
最近被一個「Geek」的美國小孩坑慘了,就是因為他「極致優化」了系統,導致系統複雜度指數級上升(最後我讓他跟我go through一邊code,他說自己也已經看不懂了)… 我所理解的Geek,是無所不用其極的去解決問題,在SDE眼裡,不應該有「解決不了的問題」,只有「不同cost」的解決方案。
不要總是低頭忙於技術細節,嘗試抬頭以更高更長遠的角度來看問題,其實往往最後能幫助你更好的把握「變化」,從而幫助你設計出更加「動態極致」的系統。加油!
我們是與題目無關的小尾巴 ( ^ω^ ) 8年amazon老兵的經驗談(^-^)/
寫工業級別代碼是怎樣一種體驗? - 阿萊克西斯的回答
對於程序員來說,怎樣才算是在寫有「技術含量」的代碼? - 阿萊克西斯的回答
先去學習標準庫怎麼實現,只做最重要的事情,把寶貴的時間浪費在糾結是4條指令還是5條指令這種事情上,第一說明你太閑了,第二說明你學習的不夠。對於你的問題,不用想也能回答你:兩個都可以,建議你挑代碼短、好讀懂的。時間複雜度沒有變化的情況下永遠選擇最顯然的寫法。除非實際應用當中profiler說明這段代碼不優化就會帶來瓶頸,不要去做奇怪的優化。
完成比完美更重要。
我的答案,不妨礙,反而會促進編碼。
理由:
1,成本合理:
你寫的代碼不是為了錢,你又有時間,尤其你現在追求的代碼規模是合理的,
所以你負擔得起寫當前代碼的成本。
2,回報合理:
當針對某一需求,比如你想要極致的性能,去重寫輪子,去寫你想要的輪子,你會經歷一個:
1,不知道為什麼別人那麼寫?
2,原來別人因為這個那麼寫。
3,我可以這樣寫試試。
4,原來我這麼寫也是個不錯的選擇。
5,若干日月後的某一天,你突然發現自己的業務需求有所變化,而這個變化可以直接映射到你腦子裡,曾經輪子結構的某一處,Ok,輪子稍加修改,就可以以結構優美,性能可控,成本低廉的漂亮姿勢完成業務了。
6,一個幾率不小的事情,你會發現,有時候不是說大家都追,都捧得就是好代碼,又懶又笨的人是大多數,獨立思考而踐行的你,未必不能寫出更好的。
---
很多原理性,標準性的東西,以自己的追求去寫一遍絕對比只讀更能理解其中的奧妙。
軟體是一個非常複雜的工程,積累是唯一的出路,不管從哪一個層次開始,你現在投入精力寫的優秀代碼,都會在將來幾倍的回報你。
不然我們寫軟體幹啥?
你只是代碼寫的太少而想得太多
我覺得至少你要遵循幾個常見的編程原則:
1.不要過早優化。
2. KISS原則,keep it simple and stupid.
3. DRY原則,Don"t repeat yourself.
4. 代碼是給人看的,其次才是讓機器執行的。
你想優化的想法是好的,但是即使是優化,也是內部優化,你提供的介面使用起來依然是簡單的。
建議你參考下stl源碼,看看你的疑惑別人是怎麼權衡的。
給你幾篇參考文章:
編程的首要原則(s)是什麼? -劉未鵬
代碼的抽象三原則-阮一峰
關鍵是數據結構這個課和真正的編碼沒啥關係啊。數據結構大學裡是當一門理論學科來教的。核心目的是讓你掌握數據結構的理論。直接鑽進編碼的坑是捨本逐末了。你要學的數據結構有好多,以後複雜的平衡樹,fibonacci 樹後綴樹各種結構你連理解基本理論都難。你去網上找實際implementation更是基本都找不到的。這種簡單的變長列表還要糾結編碼效率問題等你學到高級數據結構和演算法是要坑死自己的。自己還沒學到那個層次的時候不要妄圖把自己拔苗助長了。這個不叫geek叫鑽牛角尖了。推薦你編碼的時候注重實現數據結構本身的功能。把code保存好,以後有更深的理解的時候回頭再修改。這樣進步更快。內功不到位強行學七傷拳會死的
要寫出好的代碼,有時候你要先寫出壞的代碼。
長年工程項目的要求都是能用,夠用,好用穩定,這樣發展的
記得有個室友經常教育我說「不要過度設計」……
然而總是一不小心就過度設計了…
然後各種東西就各種搞不完…
到Deadline之前一晚上…
各種砍需求各種趕工…
熬通宵……
在實習的時候,我也是一開始就想寫出優雅,解耦,有擴展性,功能範圍不局限於當前需求的代碼。
後來發現這樣會太專註於細節,耗時長,可能會不能很好的適應業務變化(當然也可能對業務變化友好)。
還是先完成業務功能,等有業務稍微穩定或者業務性能提升需求。再來深度優化。
當然,平時寫的時候不能寫很蠢的代碼。
做好封裝和解耦,然後隨便你怎麼geek
編程水平不行就不要叫極客了,好不?!
那你的意思是編碼簡單就好咯?
那排序就直接冒泡什麼快排歸併都不要寫好了。
但明顯不是這樣的,stl寫出來是要給大家用的,效率不行浪費的是千千萬萬使用者的時間,這樣比較起來,寫代碼只是很少的一部分時間。用更多的時間去編寫代碼使其效率更好是划算的。
編碼=encoding
編程=programming
首先不同實現在不同場景性能不同 其次你在覺得在你的場景不行 你倒是自己擼啊 光想我無法認可題主的geek呢
實際上你說的是geek精神和軟體工程的影響。
geek也編碼,追求的一種極限。
軟體工程也編碼,有明確的功能性能需求。
他們的目標不同而已。
你想明白這個區別就不會有這個問題了。
vector前一段時間我也寫了一個玩,放段代碼給你看一下
template&
iterator&
size_t len = pos - cbegin();
if(count + n &> volume){
volume = volume ? 2 * volume : 1;
while(volume &< count + n) volume *= 2;
T* tmp( (T*) new char[volume * sizeof(T)] );
memcpy(tmp, array, len * sizeof(T));
for(size_t i = 0; i &< n; i++) new (tmp[len + i]) T(value);
memcpy(tmp + len + n, array + len, (count - len) * sizeof(T));
delete [] (char*) array;
array = tmp;
}
else{
memmove(array + len + n, array + len, (count - len) * sizeof(T));
for(size_t i = 0; i &< n; i++) new (array[len + i]) T(value);
}
count += n;
return begin() + len;
}
移動元素是用memcpy和memmove實現的,對於一些移動操作比較特殊的類可能會出錯。
——————————————————————
不要因為細小的性能損失妨礙了自己的編碼思路,怎麼舒服怎麼寫。
優先實現功能,再考慮運行效率。
推薦閱讀: