程式設計師雜談

多範型 .NET,第 10 部分:選擇一種方法

Ted Neward

在我最後一欄 (組件 9 本系列),我建議隨時文章系列取得接近雙精度浮點的數字,作者可能是 pretentious,足以認為他的讀者都實際想要在主旨列中,多次,或是他會太過 boneheaded,準備好新的主題。讀取器會交給 speculate 時,哪些這兩種情況下是在此工作。

不過,欄位中的註解更加清楚的儘管的周遊至兩領域、 一個 multiparadigmatic 的設計上的多個文件似乎需要嘗試結合個別的項目,以示範如何使用每個這些不同的開發架構,以及選擇其間的實際問題。「 真實,「 我們的目的而言,表示非一般不足以在如何方式可能不是簡單,選擇一篇雜誌文章中解決這些問題的有用提示。

嘗試建立如問題原來是更困難,比它可能會出現。 太多分心它以便清楚檢視使用; 的解決方案中的任一個概念是太複雜, 其概念是過於簡單,以便太少的變化,在它的實作,來說明如何不同開發架構可能每一個用來解決。幸運的是,才能開始就可使用的某些已完成的工作 — 目前 Thomas 的 「 程式碼 Katas。 」 的形式

程式碼 Katas

在他的網站 (http://codekata.pragprog.com),Thomas 會將關於的兒子扎克瑞放在 karate 的作法,以及發現 dojo 有沒有處於給他的父代的檢視區域的空間。這交給他 45 分鐘,,他開始玩一玩一些他已經考慮這過效能的影響相關的程式碼。他:

我只被想要玩一些程式碼,來試驗使用之前,我很久沒有用的技術。我沒有在簡單、 受控制的環境中,我試著許多不同的字型樣式 (超過我在這裡列出)。And I’ve still got some more playing to do …

對這項練習工作階段吗?嗯,我有一些時間,且不被中斷。我有簡單的方法是,我想要再試,和我試過多次。我討論意見反應的每次讓我無法可改善。沒有任何壓力:程式碼是有效的 throwaway。很有趣:我保留進行小步驟,那麼我繼續。最後,我是個以上的當我發生知道它用完。

最後,它不允許我要練習的可用時間。如果壓力上,如果沒有期限傳送部落格內容的搜尋功能,然後現有的效能會是可接受練習永遠不會發生。但是,這些壓力空閒的 45 分鐘讓我播放。

因此,我的一天的挑戰:如果您可以 carve 出 45 到 60,請參閱若要播放的一小部分的程式碼的分鐘數。您並不需要查看效能。 也許您無法播放以結構而定,或是記憶體使用的介面。最後,並不重要。試驗、 測量、 改善。

亦即,程式碼 kata 時 () 困難的問題 — 不難掌握在概念上來說,所提供的架構,以瀏覽。在我們的特定情況下,目標就會以設計。我們將依據我們探索上 Thomas 的"Kata 4:資料共同 」。

Kata 四個:資料共同

程式碼 kata 在此情況下有三個部分,我們會在不連續步驟中,一次瀏覽此網頁的設計,並考慮每個可供我們使用 C# 中的座標軸 (雖然,同樣地,在 Visual Basic 中的方案是同樣平易近人的)。

步驟一

1 讀取,如下所示的步驟:

Weather.dat (在http://bit.ly/ksbVPs) 您可以找到每日天氣資料Morristown,N.J.,2002 年 6 月的。下載這個文字檔案,然後撰寫程式輸出的日數 (第一欄) 與最小溫度深壓淺 (最大的溫度為第二個資料行的最小值的第三欄)。

Weather.dat 檔案看起來就像在顯示的內容圖 1 (除了它延伸完整的 30 天)。

圖 1] 的 Weather.dat 文字檔

MMU 2002 年 6 月                                
Dy MxT MnT AvT HDDay AvDP 1HrP TPcpn WxType PDir AvSp Dir MxS SkyC MxR MnR AvSLP
1 88 59 74   53.8   0.00 F 280 9.6 270 17 1.6 93 23 1004.5
2 79 63 71   46.5   0.00   330 8.7 340 23 3.3 70 28 1004.5
3 77 55 66   39.6   0.00   350 5.0 350 9 2.8 59 24 1016.8
4 77 59 68   51.1   0.00   110 9.1 130 12 8.6 62 40 1021.1
. ..                              
28 84 68 76   65.6   0.00 RTFH 280 7.6 340 16 7.0 100 51 1011.0
29 88 66 77   59.7   0.00   040 5.4 020 9 5.3 84 33 1020.6
30 90 45 68   63.6   0.00 H 240 6.0 220 17 4.8 200 41 1022.7
mo 82.9 60.5 71.7 16 58.8   0.00     6.9     5.3      

 

它會立即清除檔案不是以逗點分隔,但以位置分隔,"MxT"(最大 temp) 資料行永遠從相同的位置開始,並在另一個固定的位置開始的"MnT"(最小 temp) 資料行。在 Visual Studio 中的檔案,快速顯示每一行都是精確 90 字元; 剖析檔案--琵式來,字串的字串排列會困難,因為我們可以分割在新行字元或 90 個字元長度。

之後,不過,事情會較不清楚。雖然練習詢問我們輸出只最小溫度硬位置值和執行 String.Subset,以在每一行上的,以進行散佈的月份時,所遺漏一些我們的目標。所以讓我們進一步採取動作只需一個步驟和修正的問題也必須能夠檢查資料的任何項目為月份中的任一天。

請記住,multiparadigmatic 的設計核心是識別的共同點/變化座標軸,而這個問題到目前為止,共同點/變化座標軸是很輕而易舉,但是很容易看到我們需要剖析的文字檔,並檢查結果。該資料,基本上是表格,並可以查看幾個不同的方式,我們已經調查架構為基礎。

程序性的觀點而言,資料結構定義的每一行,且物件導向方案,並不會給我們更。它是相當簡單,只需採取的程序的資料結構,並拋要剖析的資料類別上的方法。基本上,我們回到 「 智慧資料 」,尤其是 object-ish,雖然可以運作。是的當時。

Nothing here suggests the practical use of meta-programming tactics yet, or of dynamic programming, except perhaps the data structure allowing some kind of lookup based on column name, so that day1[“MxT”] would return “88.” The functional approach could be interesting, because parsing can be seen as a collection of functions, running one after another, taking input and returning strings (or other parsed data) and more functions to parse the rest of the file as results.一種技術就所謂的剖析器 combinators,此特定文件將討論遠超過範圍。

沒有這種方法似乎幫助很多。 到目前為止,最佳的解決方案似乎是程序性的方法,並作中所示圖 2

圖 2 程序的方法

namespace DataMunger
{
  public struct WeatherData
  {
    public int Day;
    public float MxT;
    public float MnT;
    // More columns go here
  }
  class Program
  {
    static void Main(string[] args)
    {
      TextReader reader = new StreamReader("weather.dat");
           
      // Read past first four lines
      reader.ReadLine();
      reader.ReadLine();
      reader.ReadLine();
      reader.ReadLine();
 
      // Start reading data
      List<WeatherData> weatherInfo = new List<WeatherData>();
      while (reader.Peek() > 0)
      {
        string line = reader.ReadLine();
 
        // Guard against "mo" summation line
        if (line.Substring(0, 4) == "  mo")
          continue;
 
        WeatherData wd = new WeatherData();
        wd.Day = Int32.Parse(line.Substring(0, 4).Replace("*", " "));
        wd.MxT = Single.Parse(line.Substring(5, 6).Replace("*", " "));
        wd.MnT = Single.Parse(line.Substring(12, 6).Replace("*", " "));
        // More parsing goes here
 
        weatherInfo.Add(wd);
      }
 
      Console.WriteLine("Max spread: " +
        weatherInfo.Select((wd) => wd.MxT - wd.MnT).Max());
    }
  }
}

請注意這段程式碼已產生問題。 剖析到達日 9,星號會出現在 [MnT] 欄中,表示這 「 低 」 的月份,如同天 26 時 「 高 」 的月份。 這可解決方式除掉"*"的字串,但是點出進行:程序的座標軸將重點放在建立資料結構和操作它 — 在這種情況下,剖析它。

另請注意 [使用清單 < > 到此; 因為我們使用程序性的方法來剖析檔案並不表示我們無法利用有用類別的其他地方基底類別程式庫中。 這可予以簡化找出最小的模式,在單一物件 LINQ 查詢將會提供所需的結果。 (我們會的本課程中,哪一天是忠實的問題,該模式會發生,但這是真的只要傳回與原始的浮點; 以外的任何項目上的最大值 () ing 抓取的 請考慮為讀者練習。)

步驟二

我們可以花大量時間規劃如何我們可能會寫入 「 可重複使用 「 程式碼,但是也會為一樣來預測未來; 這段程式碼的運作方式,因此讓我們繼續在 kata 中的下一個步驟:

[檔案] football.dat ( http://bit.ly/lyNLya) 包含的結果 英文首要聯盟 2001年/2002。 資料行標記"F"和"A"包含分數,針對每個小組的目標,季節 (使集合奪得 79 目標針對對手,並且有 36 目標分數對付他們) 的總數。 撰寫程式,列印的最小的差異,在"for"和"對"目標與小組的名稱。

更多的文字,剖析。 享受這。 Football.dat 檔案會顯示在圖 3。 它很類似,在許多方面 weather.dat (圖 1)、 尚未充分不同於一些不同剖析程式碼來。

圖 3 Football.dat 的文字檔

  小組 P W L D F   A 點數
1. 集合 38 26 9 3 79 - 36 87
2. 利物浦 38 24 8 6 67 - 30 80
3. Manchester_U 38 24 5 9 87 - 45 77
4. Newcastle 38 21 8 9 74 - 52 71
5. Leeds 38 18 12 8 53 - 37 66
6. Chelsea 38 17 13 8 66 - 38 64
7. West_Ham 38 15 8 15 48 - 57 53
8. Aston_Villa 38 12 14 12 46 - 47 50
9. Tottenham 38 14 8 16 49 - 53 50
10. Blackburn 38 12 10 16 55 - 51 46
11. Southampton 38 12 9 17 46 - 54 45
12. Middlesbrough 38 12 9 17 35 - 47 45
13. Fulham 38 10 24 14 36 - 44 44
14. Charlton 38 10 14 14 38 - 49 44
15. Everton 38 11 10 17 45 - 57 43
16. 保頓 38 9 13 16 44 - 62 40
17. Sunderland 38 10 10 18 29 - 51 40
18. Ipswich 38 9 9 20 41 - 64 36
19. Derby 38 8 6 24 33 - 63 30
20. 列斯特 38 5 13 20 30 - 64 28

 

如果我們獨立考慮這兩個程式,它很容易知道,個別,每個解決以相同的方式,並沒有很多的點重新足球的練習 1 中。

步驟 3

最後一個步驟要 kata,不過,通往我們工資泥土練習的:

需要兩個先前撰寫的程式和比例出較通用的程式碼,以讓您使用兩個較小的程式和某種共用功能。

這是有趣,並分析右邊來變化的共同點/分析。

共同點/變化

因為我們已經檢查以上問題 — 也就是說,一系列的問題 — 它會比較容易 tease 出一般與變數之間的兩個問題。 該分析,開始分析的文字檔中,會顯示練習基本上 boils 到兩個步驟:剖析檔到清單中的資料列資料,並再檢查方面某種計算或分析資料。

有一些剖析文字檔案時要考慮的問題:

  • 這兩種檔案格式有 「 標頭 」 需要被忽略。
  • 這兩種檔案格式以位置為基礎。
  • 這兩種檔案格式是要忽略的線路 (weather.dat,在 [mo] 總和列"------"中 football.dat 的視覺標記)。
  • Weather.dat 會有一些空的資料行。 football.dat 則否。
  • 這兩種檔案格式支援 (class) 數值和字串的資料行 (不同之處在於 weather.dat 也包含 「 * 」 可能需要以某種方式擷取的值)。

計算結果極大的何種剖析的資料最後一樣,因此它似乎合理的作法那里開始。 有幾種方法,根據每個開發架構,我們無法從中開始:

程序這個 x 軸稱為著重於擷取資料結構的共同點。 不過,有兩種不同的檔案格式,這就很明顯地我們想要用來擷取變化,因此程序看來快要開機。 它可能會建立某種資料結構,可擷取這兩種檔案格式,但它可能是要看相較於其他的開發架構。

物件導向兩者之間的共同點檔案使用基底的抽象"TextParser"類別提供基底的剖析功能,包括能夠略過行 
suggests。 剖析每一行,表示子類別必須覆寫 「 ParseLine 」 的方法,來執行實際的琵式剖析某種隨附變化。 如何剖析擷取所有值的 TextParser 子型別,但是,為了執行最小/最大比較,可能很難處理,因為資料行型別也會有所不同。 在過去,Microsoft。NET Framework 解決了這 (使用 SQL 的資料集),藉由傳回物件,就可以在必要時使用。 但是,引入可能的型別安全錯誤,因為物件必須是相當有用,向下轉型,有可能有危險。

中繼數個不同的解決方案階段逐一浮現以及中繼-物件/meta programmatic 範圍。 Attributive 方法建議 TextParser 類別無法採用 「 記錄 」 型別,其中每個所述的資料行具有開始 (長度) 的自訂屬性,描述如何剖析的行,如下:

public struct WeatherData
{
  [Parse(0, 4)]
  public int Day;
  [Parse(5, 6)]
  public float MxT;
  [Parse(12, 6)]
  public float MnT;
}

TextParser 然後無法取得記錄類型 (TextParser <RecordT>),並在執行階段使用的自訂屬性,來探索如何剖析每一行參數化。 然後這會傳回清單記錄 (清單 [<RecordT>]),我們可以從中上述計算。

此外,generative 的程式設計建議某種類型的來源格式描述文字檔案,且每一種檔案的剖析器會產生該來源的格式為基礎。

動態的動態方法是有點奇怪,指定它是較新的方法。NET 中,但我們想像這裡取得字串,描述如何剖析每個檔案的 TextParser 類別。 幾乎希望使用小的程式設計語言,在它自己的右邊,可能加入規則運算式,或 (在本例中) 只是使用位置的資料,因為這是這個問題只需要:

string parseCommands =
  "Ignore; Ignore; Ignore; Ignore; Repeat(Day:0-4, MxT:5-6, MnT:12-6)";
TextParser parser = new TextParser(parseCommands);
List<string> MxTData = parser["MxT"];
List<string> MnTData = parser["MnT"];

這是相當不同的中繼方法,在於它缺少中繼方法可提供型別安全。 不過,中繼方法需要編譯時期變更如果變更的檔案格式,而一個動態,因為它是在名稱為基礎的變化,無法回復變更。 動態方法在內部提供更大的彈性更為複雜。

什麼在執行階段傳遞,而不是在原始檔時的執行階段建立剖析器可能 generative 的中繼方法所使用的來源格式。

功能最好奇心,功能性方法會在某些方面,它會建議的演算法 — 剖析 — 是成績的變異性所在的位置,並因此 TextParser 類別應該採取一或多個描述如何剖析文字時,是否琵式的函式或資料行-依資料行,或兩者。 例如,TextParser 需要知道如何做兩件事是忽略非剖析的行和行細分為構成組件。 這些可以當做函式的執行個體上 TextParser 本身,如所示,傳入的透過建構函式或一組屬性,透過建立圖 4

圖 4 功能性方法

TextParser<WeatherData> parser = new TextParser<WeatherData>();
parser.LineParseVerifier =
  (line) =>
  {
    if ( (line.Trim().Length == 0) || // Empty line
         (line.Contains("MMU")) ||    // First header line
         (line.Contains("Dy")) ||     // Second header line
         (line.Contains("mo")))
        return false;
    else
      return true;
  };
parser.ColumnExtracter =
  (line) =>
  {
    WeatherData wd = new WeatherData();
    wd.Day = line.Substring(0, 4);
    wd.MxT = line.Substring(5, 6);
    wd.MnT = line.Substring(12, 6);
    return wd;
  };
List<WeatherData> results = parser.Parse("weather.dat");

TextParser 使用參數化型的別只是要擷取的 WeatherData 型別更高的型別安全。 它可以傳回泛型 System.Object,如有需要,或如果更容易撰寫。

完成

很顯然有是不實際 「 單向 」 解決此問題,當其他需求進入焦點時,我們會更具體的 variabilities 的所在,而因此更好的有意義的共通性,需要擷取。 這樣一來,順便一提,反白顯示重構的實際優點:因為我們無法預測未來,重構程式碼表示我們可以是錯誤的相關我們的共同點/變化的分析,但不卻"丟棄它全部,然後重新開始"時這些錯誤來燈。

Multiparadigm 的設計並不容易受到掌握,尤其是不是在一次。 要花 10 個發行項來描述,而且相當合理預期會花較長的時間納入您自己的想法。 但是這樣做可能會導致更有彈性,並能輕鬆地設計階段,並甚至會讓其他開發人員在您要查看問題的迷霧透過該方案的管理方式有點 wild-eyed 因問題提供解決方案。 請記住,在結束時,multiparadigmatic 的方法開始,結束的共同點和變化 —,請記住,和許多事情會開始找出本身。

朝向這個終點,我強烈建議讀者採取資料共同 kata 刻意嘗試解決每個使用五個不同的開發架構的每個個別的兩個檔案剖析練習再尋求將它們結合以便建立功能強大且可重複使用的程式碼的方法。 物件導向成對向上與功能或動態時,會發生什麼事? 簡單或複雜,解決方案是否已可取得吗? 然後採用的方法,並在它擲回不同的檔案格式 — 方式並不會以 CSV 檔案格式修改它? 更重要的是,執行這項操作期間一些空的時間之間的本文或專案的會議。 從這所獲得的經驗會付清西式拱豬] 當您嘗試執行這種設計,在實際執行期限內的射擊。

如先前所提到,但值得重複:multiparadigm 的語言會此處、 共同使用和看起來像是它們保持在這裡。 各種 Visual Studio 2010年語言每個有某種程度的每個這些範例。 C + +,比方說,有一些參數的 meta-programming 設備不是在 managed 程式碼,程式的方式,c + + 編譯器的運作方式。 而且是最近才 (在最新的 C + + 0x 標準) 它獲得 lambda 運算式。 即使許多 maligned JScript/ECMAScript/JavaScript 語言可執行物件、 程序,meta-programming 和動態且正常運作的範例。 事實上,大部分的 jquery 也依據這些概念。 這些概念在您的大腦中採取根的更快、 更快可以編寫您的情況下透過 stickiest 的方式。

祝各位寫程式!

Ted Neward 是主體與 Neward 和 建立關聯,獨立的公司,專精於企業。NET Framework 並 JAVA 平台的系統。他所撰寫的 100 個以上的文件、 是 C# MVP,INETA、 講師和曾著作或與他人合著過十幾書籍,包括 「 專業 F# 2.0 」 (Wrox,2010年)。他查閱並定期 mentors,到達他在 ted@tedneward.com,如果您想要讓他來自您的小組使用,或讀取他的部落格,在 http://blogs.tedneward.com

感謝給下列技術專家來檢閱這份文件: 陳 d。 綠色