回顧自己寫網頁程式的歷史,一路換過好幾種語言,從Perl、VB6(寫COM+元件)、VBScript(ASP)、VB.NET(ASP.NET 1.0)一路到現在熟悉的C#(其實也分.NET 1.1、.NET 2.0、.NET 3.5)、Javascript。對寫程式一段時間的老骨頭來說,養成"運用程式指令組裝出演算邏輯以達成目的"的基本技能後,其實換個語言並不是太難的事--先用原來最熟悉的語言(語言S)構思演算法,再進行翻譯,找出目標語言(語言T)裡在語言S各保留字、語法、函數的對應,多半就能在另T語言裡實現出自己想要的功能。

我工作接觸的系統環境充滿了歷史痕跡,新舊並陳糾葛交錯,十年前阿公級ASP跟上月寫好的ASP.NET 3.5 + RIA Service + Silverlight被安裝在同一台Web Server上並行不悖,協同運作。(很意外嗎? 回想我小時候,除了在溪邊看小魚逆流,也曾經一學會新技術就想拿來翻掉舊系統以享其利,當時公司前輩聽完我熱血激昂的陳詞,臉上浮現一抺神祕而複雜的苦笑並搖搖頭。在工作多年以後,我才懂那苦笑背後的意義... 去變動一套處於堪用狀況的重要系統,其艱難度不下於推翻滿清,需要很多革命烈士抛頭顱撒熱血才能成就,而更有趣的是,開第一槍的傢伙往往是被人推出去才開火的)

有時一個功能擴充需求會一口氣扯上VB6、VBScript、C#、Javascript、XAML,好不熱鬧。這種案子改下來,常常會出現在VBScript指令結尾加;,在Javascript寫出for (int i = 1…)的錯亂。同時,將A語言演算法強轉塞進B語言的做法雖堪用,卻也存在著不圓滿。不同語言常有其解決特定問題獨到的函數、工具,萬用邏輯演算法或許可以跨語言適用,但放著鈄口鉗不用,堅持拿老虎鉗剝線(能舉出這種例子,我真不愧是水電工本色呀!),怎麼說都不算明智之舉。然而,我們在切換程式語言時,卻常會犯下類似毛病。

前幾天我找到一個實例,程式函數接入一個階層代碼字串A1.B2.C3.E4.D5.F6.G7.H8.I9代表階層資料,我需要將其展開列舉出其上層、上上層...直到第一層所有上層結構的階層代碼,例如:

  • A1.B2.C3.E4.D5.F6.G7.H8.I9
  • A1.B2.C3.E4.D5.F6.G7.H8
  • A1.B2.C3.E4.D5.F6.G7
  • A1.B2.C3.E4.D5.F6
  • A1.B2.C3.E4.D5
  • A1.B2.C3.E4
  • A1.B2.C3
  • A1.B2
  • A1

拿到這個題目,我第一個想到的做法是用Split將各層識別代號拆成字串陣列,再用For迴圈組出層數愈來愈少的階層代碼。這個演算法在C、Java、Perl、VBScript、Javascript上都可以通用:

排版顯示純文字
string src = "A1.B2.C3.E4.D5.F6.G7.H8.I9";
 
string[] p = src.Split('.');
for (int i = p.Length; i > 0; i--)
{
    StringBuilder sb = new StringBuilder();
    for (int j = 0; j < i; j++)
    {
        if (j > 0) sb.Append(".");
        sb.Append(p[j]);
    }
    Console.WriteLine(sb.ToString());
}

不過,再一想,這種寫法真的已充分發揮.NET的優勢嗎? 於是我想到可以String.Join來取代將字串陣列組合成單一字串;另外,用List<String>可移除元素的特性,逐次將最尾端的階層識別代碼移除,再用ToArray()轉成字串陣列,就可以做到逐層上升。

於是,我想到更簡潔的寫法:

排版顯示純文字
string src = "A1.B2.C3.E4.D5.F6.G7.H8.I9";
 
List<string> lst = new List<string>(src.Split('.'));
do
{
    Console.WriteLine(string.Join(".", lst.ToArray()));
    lst.RemoveAt(lst.Count - 1); //Remove the last one
} while (lst.Count > 0);
      

很顯然,第二種做法"更.NET"。

當我們在切換語言時,若對新語言了解不夠、經驗不足,就很有可能寫出一堆堪用但古怪的"C#皮VBScript骨"或"Javascript皮C#骨"程式;當我們對別人說"我會用XXX寫程式",或許只意味我們有本事用自己方法操控某種語言達成目標,未必代表能善用該語言特性,寫出簡潔、高效率的"好程式"。

以我自己的經驗,往往是在對某個語言有更深入了解後,才驚覺過去慣用的寫法猶如脫褲子放屁或拿湯匙吃麵條,愚不可及。不過,這倒也沒什麼好遺憾,並不是每個人都像張無忌可以在數個時辰內練成乾坤大挪移,學習程式語言需要恆心毅力也需要時間,實務上幾乎沒有老板願意等待你練成神功再大開殺戒(到時市場大餅應該只剩下餅屑可撿),程式設計人員免不了會在還不熟悉新語言具時就得硬著頭皮先上,但千萬要保持一顆敬畏謙卑的心,持續學習觀摩,不斷提升自己對語言工具的掌握度。而如果可以,試著讓自己長期專注在較少的語言平台上,持續累積經驗與知識,對於提升程式品質也較有利。畢竟,要才能。以我個人觀點,若某人跟我說他什麼程式語言都會寫時,有兩種可能:

他真的是百世難尋萬中選一的天生練武奇才,或者...


[圖片來源]


Comments

# by DAVE

原來會太多技能,下場只能要飯啊~~

# by laneser

看...要飯的技巧: static void Main(string[] args) { string src = "A1.B2.C3.E4.D5.F6.G7.H8.I9"; foreach (var idx in LastIndexOfCharsIncludeEnd(src, '.')) Console.WriteLine(src.Substring(0, idx)); } static IEnumerable<int> LastIndexOfCharsIncludeEnd(string str, char c) { int i = str.Length; while (i >= 0) { yield return i; if (i == 0) break; i = str.LastIndexOf(c, i-1); } } --- 沒有黑暗大寫的易懂, 這點是比較糟糕的

# by 裝笑幃

## python version src = "A1.B2.C3.E4.D5.F6.G7.H8.I9" foo = src.split('.') print [".".join(foo[0:len(foo)-x]) for x in range(len(foo))]

# by 裝笑幃

// Javascript var src = "A1.B2.C3.E4.D5.F6.G7.H8.I9"; var foo = []; while (src) { foo.push(src); src = src.substring(0, src.lastIndexOf('.')); } alert(foo);

# by 裝笑幃

// Groovy def src = "A1.B2.C3.E4.D5.F6.G7.H8.I9"; def foo = [] src.split('\\.').each { it -> foo << ((foo) ? foo[-1] + "." + it : it) } println foo.reverse()

# by pico2k

//C char str[] = "A1.B2.C3.E4.D5.F6.G7.H8.I9"; int i = 0; int idx = 0; for (i=7;i>-1;i--){ idx = 2 + (i*3); str[idx] = '\0'; printf("%s\n",str); }

# by pico2k

//C char str[] = "A1.B2.C3.E4.D5.F6.G7.H8.I9"; int i = 0; int idx = 0; for (i=7;i>-1;i--){ idx = 2 + (i*3); str[idx] = '\0'; printf("%s\n",str); }

# by litfal

我第一直覺也是用 LastIndexOf + Substring 耶 //C# string src = "A1.B2.C3.E4.D5.F6.G7.H8.I9"; int count = src.Length; do { Console.WriteLine(src.Substring(0, count)); count = src.LastIndexOf('.', count - 1, count); } while (count >= 0);

# by focus1921

要「專」才能「精」。感謝分享

Post a comment