2012年12月19日 星期三

[Python]any() function

Python有個少見的Build-in Function叫做any(),這個function需要一個iterable的參數。如果此iterable中有任何一個item是true的話,any()會return True,否則的話就return False(包含iterable是empty的情況)。

any()和and、or一樣是Short-Circuiting Operator,也就是說碰到第一個item為True any()就會直接return True不繼續往下執行。

以下是兩個使用any()的例子:

1.判斷一個list中是否有負數
if any(x < 0 for x in numbers):
    print "some of the numbers are negative"
else:
    print "none of the numbers are negative"
2.檢查密碼是否含有一個大寫字母、一個數字、一個小寫字母
def checkPassword(data):
    'Return True if password strong and False if not'
    result = any(c.isupper() for c in data) and 
             any(c.isdigit() for c in data) and 
             any(c.islower() for c in data)
    return result

[C]使用getopt()來parsing command line的參數

在Linux底下我們經常會使用'-'來設定一些參數,比如說grep使用-n來設定顯示搜尋到的行數。
grep -n "test" ./*
當我們自己寫Linux程式時有沒有比較快的方法能parsing這些參數呢?Linux底下有個好用的function叫getopt(),他可以幫住我們來parsing這些參數。要使用getopt()必須要include unistd.h,以下是一個範例:
#include <stdio.h>
#include <unistd.h>

int main(int argc, char* argv[])
{
    char ch;
    int count;
    while ((ch = getopt(argc, argv, "d:t")) != EOF) {
        switch (ch) {
        case 'd':
            printf("-d %s\r\n", optarg);
            break;
        case 't':
            printf("-t\r\n");
            break;
        default:
            fprintf(stderr, "Unknown option: '%s'\n", optarg);
            return 1;
        }

    }
    //skip the option we read
    argc -= optind;
    argv += optind;

    for (count = 0; count < argc; count++) {
        puts(argv[count]);
    }
    return 0;
}
sway@mac$ gcc getopt.c -o getopt
sway@mac$ ./getopt -d test -t apple banana
-d test
-t
apple
banana
這邊要注意的是第8行getopt()的第三個參數"d:t"。d後面的:表示-d之後需要再指定一個參數,而此參數可在optarg中取得。t後面沒有:號表示之後不需要再指定參數。

第27行的puts會把不屬於-d -t的剩下所有參數都印出來,optind其實就是前面while迴圈處理到第幾個參數的index。

2012年12月17日 星期一

Head First C(深入淺出C)


這本書延續了Head First系列的傳統,用有趣的圖片和例子帶領大家更深入認識C。就如作者前言所寫,這是一本寫給已經學過C或是學過其他程式語言的人看的。書中只花了一個Chapter帶大家復習一下C的語法。因此不要被書名給騙了,這本書不適合完全沒有程式經驗的人看。如果你只是想學習C的語法這本書也不適合你。

我認為這本書很適合當C的第二本書,書中大部份篇幅都在講解一些較進階的議題。比如說程式在執行時的記憶體區段(stack, heap, code);為什麼char *str = "hello"無法被修改;指標以及函式指標的詳細解釋;union和bitfield。這些部分搭配深入淺出系列特有的圖型教學法,讓讀者可以很輕易地瞭解這些議題,這點是本書做的最好的地方。

這本書另一個值得推薦的地方是他介紹了一些其他C教學書不太會講到的地方。比如說gcc的參數,動態連接和靜態連接,Makefile的撰寫。這些都是實務上很常用到的東西。作者解釋的也非常好,難易適中。這也是為什麼我推薦這本書當作C第二本的原因。

書中後半段介紹了一些更深入的議題:process, fork, thread, socket, IPC。這部分大致上只是帶你了解有這些東西而已,更深入的部分還是建議去看其他專門探討此議題的書。

這本書的定位我覺得是在新手到進階之間,書中講解的議題是每個進階的C程式設計師都要知道的。很適合學過C想更進階的人看,看完這本書相信對C會有更深一層的認識。

2012年11月27日 星期二

Head First Object-Oriented Analysis and Design(深入淺出物件導向分析與設計)

我一直都很喜歡深入淺出系列的書籍。這本深入淺出物件導向分析與設計就是一本深入淺出系列經典的例子,用實際生活上的例子穿插有趣的圖案一步一步帶領讀者進入OOAD的世界。書中介紹了一些基本的OO原則、部份Design Patten、UML以及軟體開發的流程,幾乎可以說涵蓋了大部分軟體開發的基本議題,很適合當OOAD初學者的第一本入門書。

其中我最喜歡書中一步一步帶你思考的模式,作者會先從不好的設計開始講起,一步一步帶你運用一些OO的基本原則修改程式架構。此外書中舉的例子也十分有趣,搭配作者幽默的文字會讓你有種在讀故事書的感覺,不知不覺就看完整本書。

書中列出開發偉大軟體的三步驟,我覺得分類的非常好。開發軟體不脫離這三個步驟:
  1. 確認你的軟體做客戶要它做的事。
  2. 應用基本的OO原則,增加軟體的彈性。
  3. 努力達成可維護、可重利用的設計。
不過這本書也像其他深入淺出系列一樣,是屬於入門類型的書籍,比較不適合有豐富OO經驗的軟體工程師。此外書中範例程式都是使用Java,讀者可能需要一些Java基本的OO知識才能閱讀。

總而言之,這是一本很棒的OOAD入門書。看完這本書後可以接著針對書中的各個議題去看進階的書籍。比如說Design Patten可以接著看深入淺出系列另外一本有名的書深入淺出設計模式

2012年11月1日 星期四

[C#]建立資料夾範例程式

C#可透過System.IO.Directory.CreateDirectory(dirPath)來建立資料夾
如果dirPath中有不存在的資料夾,CreateDirectory會順便建立此資料夾。
下列範例會在C:\底下建立一個TestDir
using System;
using System.IO;

public class Program
{
    public static void Main()
    {
        string dirPath = @"C:\TestDir";
        if (Directory.Exists(dirPath))
        {
            Console.WriteLine("The directory {0} already exists.", dirPath);
        }
        else
        {
            Directory.CreateDirectory(dirPath);
            Console.WriteLine("The directory {0} was created.", dirPath);
        }
    }
}

[C#]在C#呼叫Native DLLs (P/Invoke)

在C#我們可以透過PInvoke(Platform Invocation Services)來access unmanaged DLL中的function, structs甚至是callbacks function。以下是一個簡單的例子,示範如何在C#中呼叫Windows DLL user32.dll。
MessageBox在user32.dll宣告長這樣:
MessageBox(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
C#範例程式:
using System;
using System.Runtime.InteropServices;    // For StructLayout, DllImport

class MsgBoxTest
{
    [DllImport("user32.dll"]
    static extern int MessageBox(IntPtr hWnd, String text, String caption, int type);

    public static void Main()
    {
        MessageBox(IntPtr.Zero, "Text", "Caption", 0);
    }
}
有三個地方要注意。DllImport定義function位於哪個dll中,static定義這是一個global method,extern則是告訴CLR此function是實做在外部。
看到這邊好像覺得P/Invoke沒什麼,繼續看下去就知道P/Invoke為什麼這麼讓人頭痛。

如果Native function的參數是struct該怎麼處理?讓我們看一個例子:
void GetSystemTime(LPSYSTEMTIME lpSystemTime);

typedef struct _SYSTEMTIME {
  WORD wYear;
  WORD wMonth;
  WORD wDayOfWeek;
  WORD wDay;
  WORD wHour;
  WORD wMinute;
  WORD wSecond;
  WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME;
要呼叫這個function,我們必須定義一個C#的Class和C Struct有一樣的結構。
using System;
using System.Runtime.InteropServices;     


[ StructLayout( LayoutKind.Sequential )]
public class SystemTime 
{
   public ushort year;
   public ushort month;
   public ushort weekday;
   public ushort day;
   public ushort hour;
   public ushort minute;
   public ushort second;
   public ushort millisecond;
}
StructLayout告訴marshaler如何把每個C# class中的field對應到C Struct中,LayoutKind.Sequential表示每個field是根據pack-size aligned sequentially。預設的pack-size是8,也就是說所有的field都會aligned成8 bytes。我們可以在StructLayout中加入Pack attribute來修改pack-size。
[StructLayout(LayoutKind.Sequential, Pack = 1)]
GetSystemTime範例由於剛好是8 byte aligned所以不需更改pack size。
要呼叫GetSystemTime很簡單:
using System;
using System.Runtime.InteropServices;

class MsgBoxTest
{
    [DllImport("Kernel32.dll")]
    public static extern void GetSystemTime(SystemTime st);

    public static void Main()
    {
        SystemTime t = new SystemTime();
        GetSystemTime(t);
        Console.WriteLine(t.Year);
    }
}
如果不知道native function如何在C#中宣告,可以去PINVOKE.NET搜尋。此網站也提供一個方便的Visual Studio add-in可讓你在VS中直接查詢。

2012年10月30日 星期二

[C#]取得現在日期和時間(DateTime Format String)

在C#中,可以用DataTime取得目前的時間以及日期。
Console.Write(DateTime.Now.TimeOfDay);
21:15:56.0797894
Console.Write(DateTime.Now.ToShortTimeString());
下午 09:16
也可以更進一步用ToString()來把DataTime轉成我們要得格式
Console.Write(DateTime.Now.ToString("HH:mm ss tt"));
21:18 01 下午
大寫HH表示用24小時制表示,也可以改成小寫hh用12小時制
Console.Write(DateTime.Now.ToString("HHmm"));
2118
日期也可以用DataTime取得,以下是範例:
Console.Write(DateTime.Now.ToShortDateString());
2012/10/30
同樣也可以用ToString()轉成我們要得格式
Console.Write(DateTime.Now.ToString("MM-dd-yyyy"));
10-30-2012
Console.Write(DateTime.Now.ToString("ddd dd MMM, yyyy"));
週二 30 十月, 2012

2012年10月26日 星期五

Bit-Banding

什麼是Bit-Banding?

Bit-Banding是一種讓使用者atomic修改memory中某個bit的方法,比如說常見的bit set或是bit clear指令。Bit-Banding把一塊比較大memory中的每個word都對應到一塊較小memory中的每個單一bit,如下圖所示:
上圖把一塊32MB SRAM的每個word都對應到一塊1MB SRAM中的每個bit。其中比較大的memory叫做bit-band alias region,比較小的memory叫做bit-band region。當我們access bit-band alias region中的一個word時,會自動對應到bit-band region的bit。而此Read-Modify-Write的過程被設計成不可被中斷的。因此當我們做bit set以及bit clear時是atomic。

以上圖來說明,當我們把0x23FFFFE0設定成1就相當於把0x200FFFFF的bit[0]設定成1。相反地,當我們把0x23FFFFE0設定成0就相當於把0x200FFFFF的bit[0]設定成0。附帶一提,把值寫到0x23FFFFE0的bit[31:1]是沒有意義的,不會產生任何作用。

Bit-Banding的好處

  1. 減少code size,不需要使用mask對特定bit做修改
  2. 由於atomic的特性,可用來實做mutex以及semaphore

2012年10月18日 星期四

Debug-Later Programming v.s. Test-Driven Development

  • Debug-Later Programming(DLP)
    • Traditional way of programming
    • 設計架構之後開始寫程式
    • 當程式寫完後開始進行測試以及Debug
      • 測試和Debug的過程會佔據軟體開發超過一半的時間
      • late feedback(bug需要花好幾天、數周甚至超過一個月才會讓程式開發者知道)
      • 軟體開發時間拉長,而且難以估計時間
  • Test-Driven Development
    • 在寫程式前先撰寫unit test
      • Tests are small
      • 自動化測試,自動測試是TDD的關鍵
    • 通過所有測試即完成程式
    • 新增功能的步驟
      • 加入一個小的測試程式
      • 跑一遍所有的測資,檢查是否通過所有測項
      • 修改產品的程式碼到通過測項
    • TDD的好處
      • Bug變少
      • debug時間變少
      • 減少發生bug的side effect
      • 測試程式本身就是軟體文件
      • 睡覺睡得安穩,週末不會被打擾
      • 監控專案進度
      • TDD is fun

    2012年7月18日 星期三

    Python 學習手冊

    Learning Python是一本很有名的Python入門書。目前最新版是第四版,但是中文版只有翻譯到第三版,蠻可惜的(這本書是Python書籍裡面我覺得翻譯很好得一本書)。第四版和第三版最大的差別在於多了一些Python 3.0的介紹,其餘部份都大同小異。這本書已經出很久了,但是在Amazon上的評價一直不是很好。不過我覺得這本書其實沒這麼差,今天我來幫他平反一下。

    首先,有兩種人不適合看這本書:

    第一種人是程式語言新手,我甚至覺得Python新手也不適合看這本書。為什麼呢?因為這本書寫得很冗長、很多廢話,而且又喜歡在前面的章節就開始講一些比較進階和細節的技術(像是Python Object Model)。如果完全不會Python的話你一定沒辦法消化這些東西,更不用說之前沒學過程式語言的人。這也就是為什麼在Amazon評分會這麼低的原因之一。

    第二種人是已經有其他程式語言底子,但是想快速學會Python的人,這本磚頭書可是有700多頁,又很多廢話。等你看完都不知道什麼時候了。這種人我會推薦他看The Quick Python Book,一本精簡又好閱讀的入門書。

    講了這麼多缺點,來講講這本書的優點。這本書最大的優點就是他寫得夠詳細。書中提到的一些Python的細節是我在其他書裡頭比較少看到的。比如說這本書完整的介紹Python的reference以及Python Object Model,這些都是初學者學Python很容易搞混的一點。很多書籍都只有簡單帶過或是完全不提。其他像是bound unbound這類比較進階的議題也都有提到。因此這本書很適合當第二本Python的書籍。

    所以不要被他書名給騙了,初學者真的不適合看這本書。反而推薦這本書給有時間並且有一點Python基礎的人,看完後絕對會更了解Python。




    2012年6月29日 星期五

    [C#]OpenFileDialog在XP會更改working directory

    今天碰到一個奇怪的bug。我寫了一個C#的tool用來parsing一些binary的檔案,此程式運作流程如下:

    1. 用OpenFileDialog讓使用者選取parsing的binary檔案
    2. 程式讀取一個parsing過程會用到的參數設定檔(放在和執行檔同一個目錄下)
    3. parsing binary並且把結果寫到一個新的檔案

    看起來很正常的流程,在第二步讀取設定檔時卻會一直回傳找不到檔案的錯誤訊息。而且很奇怪的是這段程式在Win7上可以執行,在XP上卻不能執行。找了很久才發現在XP上執行OpenFileDialog會更改working directory,造成我用相對路徑開檔時會找不到設定檔。而Win7的working directory在執行OpenFileDialog並不會被更改。

    以下是一個簡單的範例程式可以看出working directory在執行OpenFileDialog後會被改掉:
     var dlg = new Microsoft.Win32.OpenFileDialog();
     workingDirectory = Directory.GetCurrentDirectory(); // Correct
     if (open.ShowDialog() == DialogResult.OK) {
            workingDirectory = Directory.GetCurrentDirectory(); // Wrong!!
     }
    
    上面例子執行後會發現workingDirectory最後會被設定成OpenFileDialog選取檔案的那個資料夾。
    還好網路上蠻多人碰到這個問題,解決方法很簡單,只要把RestoreDirectory設成true就可以了,這個Property在WPF以及Windows Form的FileDialog中都有實做:
     var dlg = new Microsoft.Win32.OpenFileDialog();
     dlg.RestoreDirectory = true;
     workingDirectory = Directory.GetCurrentDirectory(); 
     if (open.ShowDialog() == DialogResult.OK) {
            workingDirectory = Directory.GetCurrentDirectory(); 
     }
    
    執行後可以發現執行完OpenFileDialog後working directory不會被改掉了。至於為什麽Win7的working directory不會被改掉,推測是因為Win7的RestoreDirectory預設就是true,而XP預設為false。(此推測是錯誤的,原因是因為系統底層的差異)

    2012年6月26日 星期二

    [C#]Nullable Type

    在C#中,你不能把一個int或bool變數設定成null。比如說以下這個範例:
    bool b = null;  //以下程式編譯時會出現錯誤訊息
    int i = null;     
    DateTime date = null;   
    
    但是有時候我們使用的變數不一定有值怎麼辦? 這時候就可以用Nullable type。只要在宣告變數時候在型態後面加一個問號,此變數就變成可以被設成null的Nullable type。
    bool? nullableBool = null;
    int? nullableInt = null;
    DateTime? nullableDate = null;
    
    一般的變數可以自動轉換成nullable type:
    int i = 5566;
    int? j = i;
    
    如果要把nullable type轉換成一般變數,就需要強制轉換:
    int? i = 5566;
    int j = (int)i;
    
    當一個變數是nullable type時,會自動多一個HasValue的特性。可以用來判斷變數是不是null。如果是null的話HasValue會是false。反之有值不是null的話,就會是true。
    int? num = null;
    if (num.HasValue == true)
    {
        System.Console.WriteLine("num = " + num.Value);
    }
    
    使用nullable type最大的優點就是可以讓你程式比較不容易當掉,一些由user輸入的變數可以宣告成nullable type。如此一來當user輸入錯誤資料時候,可以把變數設定成null之後再做處理。

    2012年6月8日 星期五

    ELF中的.bss section和COMMON section

    大家都知道未初始化的global變數會被配置在.bss section中。不過在ELF中沒有這麼簡單,ELF多了一個COMMON section專門用來存放未初始化的global變數。怎麼會有兩個地方都用來存放未初始化的global變數呢?其實是有差別的,以下我們舉個例子來說明:
    int a = 1;            //.data section
    int b = 0;            //.bss section
    int c;                //COMMON section
    
    由上面例子可以知道,如果我們把global變數初始化為非0的值,會被放在.data section。如果把global變數初始化為0,會被直接放到.bss section。如果完全沒初始化的話就會被放到COMMON section。為什麽要多一個COMMON section呢?其實跟gcc linker的運作有關係。

    以上面例子為例,global變數c其實是一個弱型別。也就是說,如果我們在不同的檔案中都宣告global變數c而沒有初始化的話,gcc linker在做linking時後並不會產生error。
    //test1.c
    int c;
    
    //test2.c
    int c;
    
    上面例子是完全合法的,我們也可以定義一個強型別來把弱型別蓋掉。
    //test1.c
    int c;
    
    //test2.c
    int c;
    
    //test3.c
    int c = 1;    //strong type
    
    上面例子gcc linker在做linking時候如果讀到test1.c中的int c,linker會先把c放到COMMON section中。讀到test2.c的c時,因為發現重複定義,所以不會做處理。讀到test3.c中的c由於是強型別,因此會把之前在COMMON的c蓋掉,在.data section建立一個初始值為1的c。因次最後的結果我們只會在.data section中找到c這個變數,不會在COMMON中看到c這個變數。

    當然如果同時定義兩個強型別變數的話,Linker就會跟你抗議了。
    //test1.c
    int c = 1;    //strong type
    
    //test2.c
    int c = 2;    //strong type
    
    test2.o:(.data+0x0): multiple definition of `c'
    test1.o:~/sway/test1.c:4: first defined here
    collect2: ld returned 1 exit status
    
    由上面這些例子可以知道,COMMON其實主要的用途是用來讓linker做merge用的。因此uninitialized的global變數會被暫時放在COMMON section,等Linker做完merge之後再看情況搬到正確的section中,也可能繼續留在COMMON section。因為這種特性,大多數embedded project的linker script檔都會把COMMON section放在.bss裡頭。這樣當程式啟動時可以一併把COMMON也清為0。
    .bss { *(.bss) *(COMMON) }
    

    2012年5月31日 星期四

    [Python]讀寫csv檔教學

    Python自從2.3版之後就有提供csv module讓使用者去讀寫csv格式的檔案。簡單的說,csv(comma-separated values)就是一種data之間用特殊符號隔開的資料表示格式,通常是用逗號(,)隔開。
    比如說下列是從證交所網站抓下來的csv檔範例:
    日期,成交股數,成交金額,成交筆數,發行量加權股價指數,漲跌點數
     101/01/02,"2,104,640,545","50,472,201,894","497,889","6,952.21",-119.87
     101/01/03,"2,640,781,030","64,138,886,194","623,192","7,053.38",101.17
     101/01/04,"2,983,756,821","75,845,629,353","728,417","7,082.97",29.59
     101/01/05,"3,062,757,248","76,850,752,698","737,780","7,130.86",47.89
     101/01/06,"3,314,147,435","88,101,921,548","823,810","7,120.51",-10.35
     101/01/09,"2,610,433,477","71,323,370,369","634,137","7,093.04",-27.47
     101/01/10,"3,595,611,551","97,168,542,017","869,327","7,178.87",85.83
     101/01/11,"3,322,390,467","88,786,671,892","802,539","7,188.21",9.34
     101/01/12,"2,969,248,375","78,947,910,460","731,328","7,186.58",-1.63
     101/01/13,"3,497,838,901","99,286,437,370","819,762","7,181.54",-5.04
     101/01/16,"3,184,795,667","76,842,611,338","719,725","7,103.62",-77.92
     101/01/17,"3,720,277,205","101,133,309,290","899,597","7,221.08",117.46
     101/01/18,"4,088,756,925","113,988,641,382","1,020,420","7,233.69",12.61
     101/01/30,"4,540,373,544","143,900,658,474","1,113,727","7,407.41",173.72
     101/01/31,"5,486,734,180","162,361,181,834","1,283,951","7,517.08",109.67
    這種資料格式被大量用在試算表以及資料庫。例如大多數的資料庫都可以讓使用者把table用csv格式匯出。這些csv檔可以很輕易的用Python內建的csv module來做讀寫。
    以下是一個範例程式示範如何讀寫上面範例中的大盤指數csv檔:
    # -*- coding: utf-8 -*-
    import csv
    f = open('example.csv', 'r')
    for row in csv.reader(f):
        print row
    f.close()
    
    [' 101/01/02', '2,104,640,545', '50,472,201,894', '497,889', '6,952.21', '-119.87']
    [' 101/01/03', '2,640,781,030', '64,138,886,194', '623,192', '7,053.38', '101.17']
    [' 101/01/04', '2,983,756,821', '75,845,629,353', '728,417', '7,082.97', '29.59']
    [' 101/01/05', '3,062,757,248', '76,850,752,698', '737,780', '7,130.86', '47.89']
    [' 101/01/06', '3,314,147,435', '88,101,921,548', '823,810', '7,120.51', '-10.35']
    [' 101/01/09', '2,610,433,477', '71,323,370,369', '634,137', '7,093.04', '-27.47']
    [' 101/01/10', '3,595,611,551', '97,168,542,017', '869,327', '7,178.87', '85.83']
    [' 101/01/11', '3,322,390,467', '88,786,671,892', '802,539', '7,188.21', '9.34']
    [' 101/01/12', '2,969,248,375', '78,947,910,460', '731,328', '7,186.58', '-1.63']
    [' 101/01/13', '3,497,838,901', '99,286,437,370', '819,762', '7,181.54', '-5.04']
    [' 101/01/16', '3,184,795,667', '76,842,611,338', '719,725', '7,103.62', '-77.92']
    [' 101/01/17', '3,720,277,205', '101,133,309,290', '899,597', '7,221.08', '117.46']
    [' 101/01/18', '4,088,756,925', '113,988,641,382', '1,020,420', '7,233.69', '12.61']
    [' 101/01/30', '4,540,373,544', '143,900,658,474', '1,113,727', '7,407.41', '173.72']
    [' 101/01/31', '5,486,734,180', '162,361,181,834', '1,283,951', '7,517.08', '109.67']
    
    此程式會先把csv檔打開,之後透過csv.reader()把每一行的內容用逗號切開,回傳一個list。其實有點像是split,但是cvs.reader()會幫你把" "處理掉。這是單純用split沒辦法做到的事情。
    csv module還有提供其他好用的功能。比如說可以幫你把資料parsing成dictionary的格式,使用第一列當作dictionary的key。
    # -*- coding: utf-8 -*- 
    import csv
    f = open('example.csv', 'r')
    for row in csv.DictReader(f):
        print row['成交金額']
    f.close()
    
    50,472,201,894
    64,138,886,194
    75,845,629,353
    76,850,752,698
    88,101,921,548
    71,323,370,369
    97,168,542,017
    88,786,671,892
    78,947,910,460
    99,286,437,370
    76,842,611,338
    101,133,309,290
    113,988,641,382
    143,900,658,474
    162,361,181,834
    
    也可以自己指定key的名稱:
    # -*- coding: utf-8 -*-
    import csv
    f = open('example.csv', 'r')
    for row in csv.DictReader(f, ["日期", "成交股數", "成交金額", "成交筆數", "指數", "漲跌點數"]):
        print row['指數']
    
    發行量加權股價指數
    6,952.21
    7,053.38
    7,082.97
    7,130.86
    7,120.51
    7,093.04
    7,178.87
    7,188.21
    7,186.58
    7,181.54
    7,103.62
    7,221.08
    7,233.69
    7,407.41
    7,517.08
    
    發行量加權股價指數的資料被對應到"指數"這個我們自行命名的key,由此可知csv.DictReader會根據你所提供list的順序來自己對應到資料中。
    要寫把資料存成csv格式可以使用csv.writer(),以下是個簡單的範例:
    data = [
      [' 101/01/31', '5,486,734,180', '162,361,181,834', '1,283,951', '7,517.08', '109.67'],
      [' 101/01/13', '3,497,838,901', '99,286,437,370', '819,762', '7,181.54', '-5.04'],
    ]
    f = open("stock.csv","w")
    w = csv.writer(f)
    w.writerows(data)
    f.close()
    

    2012年5月29日 星期二

    [Python]用Python抓證交所網站過去的大盤指數資料

    今天用Python內建的urllib寫了一個可以抓過去大盤指數資料的程式
    此程式抓下來的資料是.csv檔,會自動存在C:底下
    範例程式只有抓2012年1月到5月的資料,有興趣的人歡迎自行修改拿去使用
    import urllib
    
    original_url = "http://www.twse.com.tw/ch/trading/exchange/FMTQIK/FMTQIK2.php?STK_NO=&myear=2012&mmon=%.2d&type=csv"
    urlList = [original_url % month for month in range(1, 6)]
    
    for index, url in enumerate(urlList, 1):
        try:
            urllib.urlretrieve(url, 'C:\\' + str(index))
        except IOError:
            print 'IOError'
        except urllib.ContentTooShortError:
            print 'ContentTooShortError'
    

    [Python]如何在Python排序(Python Sorting)

    Python提供兩種內建排序的function分別是sort()和sorted()
    這兩個function都可以用來排序一個list
    差別在於sorted()會回傳一個排序好新的list
    sort()會直接修改原始的list並排序完成
    以下是一個範例:
    >>> a = [2, 4, 3, 5, 1]
    >>> sorted(a)
    [1, 2, 3, 4, 5]
    >>> a
    [2, 4, 3, 5, 1]
    >>> a.sort()
    >>> print a
    [1, 2, 3, 4, 5]
    
    從以上範例可以知道,如果你確定原始的list不需要保留下來的話,可以使用sort()來排序
    如果要保留原本的list,就用sorted()來產生一個排序好的新list

    這兩個function還有另一個不一樣的地方
    sort()只能用在list上排序,而sorted()可用來排序任何的iterable(string, dictionary, tuple...)
    下列範例用sorted()來排序一個dictionary
    >>> sorted({1: 'D', 3: 'B', 2: 'B', 6: 'E', 5: 'A'})
    [1, 2, 3, 5, 6]
    

    sorted()提供了一些參數可以讓我們決定要怎麼排序
    當我們把reverse設成True的時候,會把排序的結果由預設的ascending改成descending
    strs = ['aa', 'BB', 'zz', 'CC']
    print sorted(strs)  # ['BB', 'CC', 'aa', 'zz'] 
    print sorted(strs, reverse=True)   # ['zz', 'aa', 'CC', 'BB']
    
    可以設定一個function給key這個參數,在排序之前會自動對每個element call一次key所指定的function
    透過這個方法可以做一些比較複雜一點的排序
    sorted("aa BB cc ZZ".split())                       #['BB', 'ZZ', 'aa', 'cc']   (case sensitive)
    sorted("aa BB cc ZZ".split(), key=str.lower)        #['aa', 'BB', 'cc', 'ZZ']   (case-insensitive)
    
    如果要根據某些欄位來做sort,也可以用key這個參數
    要注意的是,key只接受function當作參數。下列範例使用lambda來傳入一個匿名函式
    students = [
            ('john', 'A', 15),
            ('jane', 'B', 12),
            ('dave', 'B', 10),
    ]
    sorted(students, key = lambda x : x[2])   # sort by age
    #[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
    
    為了方便使用者使用,Python提供operator module,不用自己撰寫key的function
    以下是使用operator module中的itemgetter()的範例
    from operator import itemgetter, attrgetter
    sorted(student_tuples, key=itemgetter(2))
    #[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
    
    如果sort的物件是class object的話,也可以使用operator module中的attrgetter()來做sort
    class Student:
            def __init__(self, name, grade, age):
                    self.name = name
                    self.grade = grade
                    self.age = age
            def __repr__(self):
                    return repr((self.name, self.grade, self.age))
    
    student_objects = [
            Student('john', 'A', 15),
            Student('jane', 'B', 12),
            Student('dave', 'B', 10),
    ]
    
    sorted(student_objects, key=attrgetter('age'))
    #[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
    
    如果不使用operator module的話,也可以自己用lambda來寫一個key function
    sorted(student_objects, key=lambda student: student.age)
    #[('dave', 'B', 10), ('jane', 'B', 12), ('john', 'A', 15)]
    
    如果要先比成績再比年齡的話,用operator module很簡單就可以做到
    sorted(student_objects, key=attrgetter('grade', 'age'))
    #[('john', 'A', 15), ('dave', 'B', 10), ('jane', 'B', 12)]
    
    在Python 2.x時候sorted還有提供cmp這個參數
    不過在Python 3.0之後被拿掉了,使用Python 3.0的時候要注意不要用到舊的語法

    2012年5月28日 星期一

    [Python]for loop, list comprehension, map效能比較

    在Python中要走訪一個list有很多種方法,常見的有for loop, list comprehension, map
    但是究竟哪一種方法效能比較好呢?以下是一個範例程式可以做個比較:
    import time, sys
    reps = 10000
    repsList = range(reps)
    
    def tester(func, *args):
        startTime = time.time()
        for i in range(reps):
            func(*args)
            elapsed = time.time() - startTime
            return elapsed
    
    def forLoop():
        res = []
        for x in repsList:
            res.append(abs(x))
    
    def listComprehension():
        res = [abs(x) for x in repsList]
    
    def mapFunction():
        res = list(map(abs, repsList))
    
    print (sys.version)
    tests = (forStatement, listComprehension, mapFunction)
    for testfunc in tests:
        print (testfunc.__name__.ljust(20), '=>', tester(testfunc))
    
    這段範例程式會用三種方法分別建立一個內含10000個element的list,並且對每個element執行內建函式abs()
    以下是程式執行結果,執行環境為Python 3.2.2
    3.2.2 (default, Dec 23 2011, 11:21:15)
    [GCC 4.6.1]
    forLoop              => 0.0016720294952392578
    listComprehension    => 0.0008997917175292969
    mapFunction          => 0.0007519721984863281
    
    由以上執行結果可知map最快、list comprehension次之、for loop大概要多花前面兩種方法一倍的時間才能執行完

    為什麽map和list comprehension會跑這麼快呢?
    其實是因為這兩種方法都有經過Python直譯器的最佳化,在執行時很接近C程式碼跑得速度
    而for loop沒有經過最佳化直接在Python直譯器中執行,自然就跑得比較慢

    以上範例是針對Python內建函式(abs)所做的測試,如果改用user自己定義的函式結果會不會不一樣呢?
    下列範例會把list中的element都加上1
    import time, sys
    reps = 10000
    repsList = range(reps)
    
    def tester(func, *args):
        startTime = time.time()
        for i in range(reps):
            func(*args)
            elapsed = time.time() - startTime
            return elapsed
    
    def forLoop():
        res = []
        for x in repsList:
            res.append(x + 1)
    
    def listComprehension():
        res = [x + 1 for x in repsList]
    
    def mapFunction():
        res = list(map((lambda x : x + 1), repsList))
    
    print (sys.version)
    tests = (forLoop, listComprehension, mapFunction)
    for testfunc in tests:
        print (testfunc.__name__.ljust(20), '=>', tester(testfunc))
    
    3.2.2 (default, Dec 23 2011, 11:21:15)
    [GCC 4.6.1]
    forLoop              => 0.0016200542449951172
    listComprehension    => 0.0009100437164306641
    mapFunction          => 0.0018360614776611328
    
    執行結果出乎意料,map竟然變得比fop loop還慢。而list comprehension還是一樣快了快一倍
    由此結果可知,Python直譯器當map裡頭放的不是內建函式時,並不會做最佳化的動作
    當使用user自己定義的函式時,用list comprehension才能得到最佳的效能

    這篇文章結論如下:
    1. 當用到內建函式時,使用map可以得到最佳的效能
    2. 除了1的case以外,盡量使用list comprehension才可以得到最佳的效能
    3. for loop不管在什麽情況下都是跑最慢的
    4. 最重要的是除非有特殊效能的考量,否則以上這三種方法盡量優先考慮Python程式的可讀性

    Reference: Learning Python, 4e

    2012年5月17日 星期四

    [Python]Python和C之間的差異 - Python的型別和物件

    這篇文章主要是想幫助那些從C轉換跑道到Python來的程式設計師
    首先先解釋一個差異很大的地方,Python的型別(types)和物件(objects)

    在Python中所有的東西都是object

    string是object,int是object,function是object,連code都是object
    這點和C或是C++非常的不同
    我們可以用一個內建的function type()來得知一個object的type
    >>> s = "sway"           #"sway"是一個string的object
    >>> type(s)        
    <type 'str'>
    >>> i = 2                #2是一個int的object
    >>> type(i)
    <type 'int'> 
    >>> def hello(): pass    #hello()是一個function的object  
    ...
    >>> type(hello)
    <type 'function'>
    >>> type(hello.__code__) #hello()的code是存在一個code的object 
    <type 'code'>

    Python是動態型別(Dynamic Typing)的程式語言,和C和C++這類的編譯語言不一樣
    C和C++變數的型別都在編譯時候就決定好了
    而Python則是在執行期(Run Time)才決定type
    >>> s = "sway"    #這時候s的type是string
    >>> type(s)
    
    >>> s = 2         #這時候s的type變成int了
    >>> type(s)
    








    由上面範例可以帶出一個Python很重要的觀念
    Python variable的type完全被它所對應到的object所決定的
    這在Python中稱為reference
    每一個variable其實就像是C裡頭的pointer
    先建立object之後,才用variable指向新建立的object,相當於給object一個別名
    因此Python的variable其實就是reference

    當我們執行i = 2的時候,其實Python會先建立一個integer的object叫做2
    之後再產生一個variable並且把它指向2這個integer的object
    Python的variable其實就只是一個name而已,我們透過這個name去間接access到object
    這點是Python和C、C++很不一樣的地方,這也是大多數C programmer剛轉過來不太適應的地方
    由於Python的這種特性,很多神奇的事情就發生了
    >>> a = [1, 2, 3]
    >>> b = a
    >>> b[0] = 4
    >>> print a
    [4, 2, 3]
    很多人看到這邊可能會覺得很奇怪,明明是修改b的值怎麼連a的值也被改到了?













    其實原因很簡單,因為b = a這行程式碼其實只是把b指到a所指向的object而已
    因此當執行完這行程式碼後,a和b就都指向同一個object
    不論是改a或是改b,最後都會對應到同一個object
    這是Python初學者最容易詢問的問題之一,其實只要搞懂Python的object架構,一切就會變得很直覺

    上面範例如果object是一個不可改變(immutable)的object,情況就有點不一樣了。不可改變(immutable)的object像是:string, integer......
    因為object是不可改變的,當你嘗試去更改object的時候,會自動建立新的物件,而不會更動到所指向的object。
    聽起來有點難懂,看以下範例就知道了
    >>> a = 2
    >>> b = a
    >>> id(a)                #id(a)可以取得a指向物件的id
    17500064
    >>> id(b)
    17500064                 #a 和 b指向同一個物件(integer 2)
    >>> b = 4
    >>> print a
    2
    >>> id(b)                #b指向一個新的物件(integer 4)
    17500016
    
    由以上範例可以知道,兩個reference指向同一個不可改變的object時。因為object不可改變的特性,透過其中一個reference試圖去修改object都會自動建立一個新的物件

    相反地,兩個reference指向同一個可改變(mutable)的object時。因為我們可以直接修改object的關係,所以透過其中一個reference去修改object不會建立新的物件。會直接修改到指向的object,因此透過任何一個reference去取值看起來就好像兩邊都被改過一樣。

    All "variables" are references. 這是Python和C很不一樣的地方。

    2012年5月1日 星期二

    大話處理器 : 了解DSP.CPU及MCU基礎架構


    這本書是一本講解處理器內部運作原理的書,看得出來作者試圖用很多生活化的例子來解釋CPU內部運作的原理。大致上可以把這本書看成是一本簡單的計算機組織入門書,整本書的內容絕大多數都是從Computer Organization and Design以及Computer Architecture: A Quantitative Approach這兩本書而來。這本書的內容比較淺,作者跳過了很多細節。因此只適合完全不懂計算機組織的人來念,否則會覺得書中寫得內容太淺了。

    書中前面幾章在講解電腦的歷史,以及介紹一些常見的CPU和DSP,這部份我覺得還蠻不錯的。這本書最大的缺點就在於翻譯品質,可能是因為兩岸用語不同的關係,許多地方寫得中文名詞都有點奇怪。這點在很多大陸翻譯書都會出現,國內出版社真的該好好改善這一部份。

    總而言之,這本書可以帶你初淺的認識CPU的內部運作原理。不過詳細的細節還是建議去閱讀計算機組織的聖經 Computer Organization and Design以及Computer Architecture: A Quantitative Approach 這兩本書,這兩本書才能真正帶你瞭解CPU的世界是怎麼運作的。

    2012年4月19日 星期四

    [Python]自己定義例外處理

    Python允許使用者自己定義一個新的例外處理
    要建立一個新的例外處理很簡單:
    class TestException(Exception): pass
    
    上面程式碼定義一個新的例外處理叫TestException,並且繼承自一個Exception的Class
    什麽時候要定義一個新的例外處理呢? 當要從好幾層迴圈離開的時候就很好用
    以下是一個從許多表格中找出特定值的範例:
    found = False
    for record in table:
        for field in record:
            for item in field:
                if item == target:
                    found = True
                    break
            if found:
                break
        if found:
            break
    if found:
        print "found"
    else:
        print "not found"
    
    傳統的寫法要從最內層迴圈一層一層break出來,程式碼變得很冗長
    這時候可以定義一個新的例外處理,當找到target的時候就raise我們新定義的例外處理
    如此一來程式碼可以變得比較簡潔
    class FoundException(Exception): pass
    try:
        for record in table:
            for field in record:
                for item in field:
                    if item == target:
                        raise FoundException()
    except FoundException:
        print "found"
    else:
        print "not found"
    

    2012年4月18日 星期三

    [Python]目前執行的OS平台

    在Python中要知道目前執行的OS平台很簡單
    只需要去檢查sys.platform這個變數就可以了
    sys.platform會保存目前執行OS平台的名字("win32" or "linux2")
    因此我們可以用一行簡單的程式碼就知道目前執行的OS平台是什麽
    isWindows = True if sys.platform.startswith("win") else False
    
    這行程式碼在windows上執行時,osPlatform的值會被設成True
    在其他OS執行的時候(ex: Linux, FreeBSD...),會被設定成False

    2012年3月28日 星期三

    嵌入式系統開機流程

    1. CPU到指定的memory address抓取第一行instruction執行,有兩種常見的作法
        (1)CPU會把PC設定為一個特定的address,從這邊開始執行
        (2)CPU會把Interrupt Vector Table設定為特定的address,並且發出Reset interrupt
             在Reset的ISR中執行某個開機的function

    2. 初始化CPU (IVT的addres, SP)

    3. 把code和data從ROM或是 Flash搬到RAM上執行,通常使用DMA搬
        (有些code可直接在external flash上執行就不需要搬)

    4. 初始化板子相關設定和參數

    5. 初始化系統(ex 執行OS、GUI)

    6. 執行主程式,很多嵌入式系統會執行一個無窮迴圈

    2012年3月27日 星期二

    在Windows底下dump DLL

    問題:
    今天碰到一個bug,有一個.net程式用到_controlfp_s這個function
    在Win7底下是放在msvcrt.dll,但是在Win XP中的msvcrt.dll卻找不到這個function

    找到此問題的方法: 
    使用dumpbin把DLL的function name dump出來看
    (有點像Linux底下的object dump)

    步驟:
    1.執行Visual Studio Command Prompt

    2.C:\WINDOWS\system32>dumpbin /exports msvcrt.dll | more

    解決方法:
    更換XP和 Win7都有的function,或是在程式當中附上DLL檔

    2012年3月14日 星期三

    約耳續談軟體:探究軟體經營的根本實學

    這本書是『約耳趣談軟體:來自專案管理的現場實錄』的續集,同樣是由充滿幽默感的約耳所撰寫。大部分文章其實之前在網路上都有人翻譯。約耳還是延續他一貫幽默的風格,很輕鬆就能讀完他。和前一本不同的地方在於,這本書主要的主題是在探討如何管理一間軟體公司,其實蠻適合主管和老闆來看看。這本書可以看到約耳對待員工以及如何管理他的公司,看了會很令人羨慕,面試時候不但有豪華轎車到機場接送,還免費住在高級的旅館並且在高級的旅館舉辦party。辦公室裡頭有私人的辦公室,坐得是一張900美金的椅子。就如同約耳文章所寫得,最好的管理就是找最優秀的人進來,讓他們認同你並放手讓這些專家自己去做。當然約耳的徵才標準也是非常高,他同時也花了幾章在抱怨目前大學課程(Java學校帶來的危害,耶魯大學的演講),相信能進去他的公司都是數一數二的高手。

    其中有幾章作者特別替微軟的IE還有Office的格式做出解釋,還蠻值得一看得,有些東西真的是因為有歷史包袱在,結果就是現在使用者看到的這樣。



    2012年2月22日 星期三

    松本行弘的程式世界:成為一流程式設計師的14種思考術


    這本書是Ruby的開發者Matz所寫得書。第一眼看到可能會覺得是Ruby的教學書,但其實這完全不是一本Ruby教學書。本書主要是以開發一個程式語言的角度來探討一些程式上的問題,作者並以Ruby的角度來講解如何解決這些問題並和其他程式語言做比較。比如說講到多重繼承,作者先探討多重繼承在C++和Java的設計上分別會有那些問題,最後再講解為什麽Ruby要使用Mix-in來實做多重繼承。

    當然還有很多其他的內容,比如說探討Design Pattern,作者就用Ruby示範了一些Pattern的實做,有些Pattern改用Ruby這種動態語言來實做會變得非常直覺好懂。其他包含MVC, Unicode, Regular Expression, Floating Point......作者一開始都先簡單講解其他語言的設計方法,最後再說明為什麽Ruby要這樣設計,看完以後會覺得Ruby真的實做了很多新的東西,並且吸收了其他程式語言好得設計方式。

    看這本書前其實不太需要先學會Ruby,作者在用Ruby寫範例Code的時候都會順便解釋Ruby的語法,像我本身並不會Ruby,但是大部分範例都可以看得懂。如果你是像我一樣對如何設計一個程式語言很有興趣的話,這本書是市面上少見在探討語言本身性質的書,非常值得一看!

    2012年1月20日 星期五

    [Python]named tuple

    在Python裡頭有一個很好用的container叫做named tuple,他可以用來建立一個自己定義的class並且具有原本tuple的所有功能
    這樣說可能有點難懂,以下是一個實際使用的例子:

    Point = collections.namedtuple("Point", "x y")
    

    collections.namedtuple()的第一個參數是自己定義的class名稱。第二個參數則是class中有的data名稱所組成的字串
    由空格隔開不同的data名稱。當呼叫此function後,會回傳一個自己定義的class
    我們就可以把他當作其他Python常見的class來使用(ex: list)
    在此範例中,定義了一個namedtuple()幫我們建立了一個Point的class,其中有x和y這兩個data member
    因此可以這樣使用他:
    p = Point(100, 200)
    print(p.x, p.y)
    
    Output:
    100, 200
    

    named tuple的作法是去繼承tuple,並根據使用者給的參數產生出一個新的class,因此原本tuple的功能也可以在named tuple中使用
    x, y = p         # unpack like a regular tuple
    

    named tuple有幾個好用的地方,比如說從資料庫、csv檔的時候,可以直接讀到我們自己定義的named tuple中
    EmployeeRecord = namedtuple('EmployeeRecord', 'name, age, title, department, paygrade')
    
    import csv
    for emp in map(EmployeeRecord._make, csv.reader(open("employees.csv", "rb"))):
        print(emp.name, emp.title)
    

    或是從檔案讀入資料,下列範例從檔案讀取資料經過處理後存到用named tuple自己定義的User class中
    User = collections.namedtuple("User", "username hashed_password id")
    users = []
    for line in open(filename):
        fields = line.split(":")
        user = User(fields[USERNAME], fields[HASHED_PASSWORD], fields[ID]) 
        users.append(user)
    

    使用named tuple會直接套用預設的class template產生簡單的class,此方法讓程式碼可讀性提高很多
    使用起來也相當便利,省去了自己宣告class定義資料型態所花的時間

    Clean Code: A Handbook of Agile Software Craftsmanship

    Clean Code,看到書名就知道這是一本教你如何寫出乾淨且容易維護程式碼的一本書。這本書一開始先從最基本的命名開始講起,接著講到怎麼寫出漂亮乾淨的function以及如何撰寫好的註解。最後還講到了例外處理、Class以及Unit Test要注意的事項。

    我覺得寫程式分幾種境界,一開始初學者的時候只要想辦法把程式寫出來就好了。但是變成老手之後,要把程式寫出來變得不是那麼困難。困難的地方反而是如何寫出乾淨且容易維護程式碼。大部份的人都可以寫出電腦看得懂的程式,只有高手才能寫出乾淨簡潔、讓其他人很容易看懂的程式碼。這本書介紹了很多技巧幫助程式設計師能寫出Clean Code,很推薦學程式有一段時間的人去閱讀這本書

    書中有提到一些重要的觀念,比如說強調命名的重要性、function要盡量簡短、盡量用程式碼來解釋程式而不是註解、不要害怕重構。看完以後收穫很多。可惜這本書目前只有英文版,而且作者有用到一些艱深的單字。不知道未來會不會有中譯本出現(補充: 已經有國內出版社推出中譯本)。

    以下是一些我的讀書筆記,大家可以參考看看

    2012年1月18日 星期三

    [Python]starred expression

    Python 3.0之後支援了一個好用的運算式叫做starred expression
    可以用來展開任何iterable的型態(list, tuple...)
    使用方法很簡單,只要在list前面加上一個*就可以了
    以下是一個簡單範例:

    >>> first, *rest = [1, 2, 3, 4, 5]
    >>> first, rest
    (1, [2, 3, 4, 5])
    

    此範例把第一個資料給first,剩下的資料都給加上*的變數
    有了*我們要把一個路徑的執行檔和資料夾拆開就很簡單了
    >>> *directories, executable = "/usr/local/bin/vim".split("/")
    >>> directories, executable
    (['', 'usr', local', 'bin'], 'vim')
    

    當然Python 3.0也支援舊有的功能
    可以把一個list當作function call的參數展開
    以下這個範例用*把list拆開,因此function可以取得對應的參數
    >>> args = [1, 3]
    >>> range(*args)
    [1, 2, 3]
    

    2012年1月12日 星期四

    Clean Code - Comments

    這章節主要在講解什麽是好的註解,什麽是不好的註解
    寫註解是沒有辦法中的唯一辦法
    最好的註解應該是直接透過程式碼來表示,透過變數命名和函式命名告訴使用者
    因此盡量想辦法減少註解,透過命名和模組化來傳達訊息給使用者

  • Comments Do Not Make Up for Bad Code

    • 盡量用程式碼來解釋你的程式,而不是comments
    • Don't comment bad code – rewrite it

  • Good Comments

    • Legal Comment
      • //Copyright (C) 2003,2004,2005 by Object Mentor, Inc. All rights reserved.
        //Released under the terms of the GNU General Public License version 2 or later.
        
    • Informative Comments
      • //format matched kk:mm:ss EEE, MMM dd, yyyy
        Pattern timeMatcher = Pattern.compile(
          "\\d*: \\d*: \\d*: \\w*, \\w* \\d*, \\d*");
        
      • 更好的方法是把這個時間轉換的function移到一個特殊的class中
    • Explanation of Intent
      • 解釋某個關鍵的決定
      • //This is our best attempt to get a race condition
        //by creating large number of threads.
        for (int i = 0; i < 25000; i++) {
          WidgetBuilderThread widgetBuilderThread = 
            new WidgetBuilderThread(widgetBuilder, text, parent, failFlag);
          Thread thread = new Thread(widgetBuilderThread);
          thread.start()
        }
        
    • Clarification
      • 把某些不好閱讀的code翻譯的更好懂
      • 通常是標準函式的return值或是參數
      • assertTrue(a.compareTo(a) == 0);    //a == a
        assertTrue(a.compareTo(b) != 0);    //a != a
        assertTrue(a.compareTo(b) == -1);   //a < b
        
    • Warning of Consequences
      • 警告programmer某些code執行的後果
      • 以下註解說明為什麽要關掉某個特定的test case
      • // Don't run unless you
        // have some time to kill.
        public void _testWithReallyBigFile()
        {
          writeLinesToFile(10000000);
          response.setBody(testFile);
          response.readyToSend(this);
          String responseString = output.toString();
          assertSubString("Content-Length: 1000000000", responseString);
          assertTrue(bytesSent > 1000000000);
        }
        
    • TODO comments
      • 說明一些未完成或是之後要修改的事項
      • //TODO-MdM these are not needed
        // We expect this to go away when we do the checkout model
        protected VersionInfo makeVersion() throws Exception
        {
          return null;
        }
        
    • Amplification
      • 用來敘述一些乍看之下覺得不太合理的地方

  • Bad Comments

    • Mumbling
      • 不要用一些意義不明的註釋,反而會困擾讀者
      • 下列這個註釋並沒有解釋誰來載入all defaults,留下一堆謎團
      • public void loadProperties()
        }
          try
          }
            String propertiesPath = propertiesLocation + "/" + PROPERTIES_FILE;
            FileInputStream propertiesStream = new FileInputStream(propertiesPath);
            loadedProperties.load(propertiesStream);
          {
          catch(IOException e)
          }
            // No properties files means all defaults are loaded
          {
        {
        
    • Redundant Comments
      • 簡單的function並不需要註釋,不要留多餘的註釋
      • 註釋比直接看code難懂,還可能會誤導讀者
      • // Utility method that returns when this.closed is true. Throws an exception
        // if the timeout is reached.
        public synchronized void waitForClose(final long timeoutMillis) throws Exception
        {
          if(!closed)
          {
            wait(timeoutMillis);
            if(!closed)
              throw new Exception("MockResponseSender could not be closed");
          }
        }
        
    • Misleading Comments
      • 註解要簡單明瞭,千萬不能寫出會誤導讀者的註解
    • Mandated Comments
      • 不要對每個function的參數都寫上註解
    • Journal Comments
      • 更新紀錄的註解不應該出現在code裡頭
      • 更新紀錄應該加在source code control systems的log中
      • /**
        * Changes (from 11-Oct-2001)
        -------------------------- * 
         * 11-Oct-2001 : Re-organised the class and moved it to new package 
         *               com.jrefinery.date (DG);
         * 05-Nov-2001 : Added a getDescription() method, and eliminated NotableDate 
         *               class (DG);
         * 12-Nov-2001 : IBD requires setDescription() method, now that NotableDate 
         *               class is gone (DG);  Changed getPreviousDayOfWeek(), 
         *               getFollowingDayOfWeek() and getNearestDayOfWeek() to correct 
         *               bugs (DG);
         * 05-Dec-2001 : Fixed bug in SpreadsheetDate class (DG);
         * 29-May-2002 : Moved the month constants into a separate interface 
         *               (MonthConstants) (DG);
         * 27-Aug-2002 : Fixed bug in addMonths() method, thanks to N???levka Petr (DG);
         * 03-Oct-2002 : Fixed errors reported by Checkstyle (DG);
         * 13-Mar-2003 : Implemented Serializable (DG);
         * 29-May-2003 : Fixed bug in addMonths method (DG);
         * 04-Sep-2003 : Implemented Comparable.  Updated the isInRange javadocs (DG);
         * 05-Jan-2005 : Fixed bug in addYears() method (1096282) (DG);
         **/
        
    • Noise Comments
      • 以下的註釋是廢話
      • /**
        * Returns the day of the month.
        *
        * @return the day of the month.
        */
        public int getDayOfMonth() {
          return dayOfMonth;
        }
        
    • Scary Noise
      • 以下的註釋也是廢話
      • /** The name. */
        private String name;
        
        /** The version. */
        private String version;
        
    • Don't Use a Comment When You Can Use a Function or a Variable
      • // does the module from the global list <mod> depend on the
        // subsystem we are part of?
        if (smodule.getDependSubsystems().contains(subSysMod.getSubSystem()))
        
      • 改寫成這樣就不需要註解了
      • ArrayList moduleDependees = smodule.getDependSubsystems();
        String ourSubSystem = subSysMod.getSubSystem();
        if (moduleDependees.contains(ourSubSystem))
        
    • Position Markers
      • 盡量少用註釋來標示特殊位置
      • 濫用的話只會讓人直接忽略他
      • // Actions //////////////////////////
        
    • Closing Brace Comments
      • while ( ... ) {
           ...
        } //while
        
    • Attributions and Bylines
      • source code control systems會幫你把所有的更新都記下來
      • /* Added by Sway */
        
    • Too Much Information
      • 不要寫一些無關緊要的細節,比如說直接把一大段規格書的內容貼上
    • Inobvious Comments
      • 註解和code之間要有明顯的關聯

    2012年1月10日 星期二

    warning: suggest parentheses around '&&' within '||'

    bool isLeap(int year){
        if (year % 400 == 0 || year % 4 == 0 && year % 100 != 0)
            return true;
        else
            return false;
    }
    
    以上程式碼如果用g++ -Wall來編譯的話會在if判斷式那行產生下列的warning
    warning: suggest parentheses around ‘&&’ within ‘||’
    
    看到這個warning可能會想說是沒有括上括號,我們整理一下上面的判斷式
    if (((year % 100) != 0) || ((year % 400) == 0) && ((year % 4) == 0))
    
    奇怪,怎麼改成這樣還會有warning?
    原來原因是發生在&&和||混合使用,但是沒有加上括號表示其運算的先後順序
    由上表可以知道&&的運算優先順序是比||來的高
    閏年的判斷式compiler其實是後面的((year % 400) == 0) && ((year % 4) == 0)會先算
    而不是(year % 100) != 0) || ((year % 400) == 0)先算,從左到右計算
    雖然結果一樣,但是運算的先後順序不一樣
    最好的方法當然是加上括號,這樣就不會造成誤會了
    在此例,正確的邏輯應該要改成這樣
    if (((year % 100) != 0) || (((year % 400) == 0) && ((year % 4) == 0)))
    
    統一把&&或是||加上括號就不會產生warning

    2012年1月5日 星期四

    Clean Code - Functions

    這章的內容是在教你怎麼寫出簡潔的function
    以下是一個錯誤示範,你花三分鐘可能還看不懂他在寫什麽
    public static String testableHtml(PageData pageData, boolean includeSuiteSetup) throws Exception {
                WikiPage wikiPage = pageData.getWikiPage();
                StringBuffer buffer = new StringBuffer();
                if (pageData.hasAttribute("Test")) {
                    
                    if (includeSuiteSetup) {
                        WikiPage suiteSetup =
                        PageCrawlerImpl.getInheritedPage(SuiteResponder.SUITE_SETUP_NAME, wikiPage);
    
                        if (suiteSetup != null) {
                            WikiPagePath pagePath = suiteSetup.getPageCrawler().getFullPath(suiteSetup);
                            String pagePathName = PathParser.render(pagePath);
                            buffer.append("!include -setup .")
                                .append(pagePathName)
                                .append("\n");
                        }
                    }
    
                    WikiPage setup = PageCrawlerImpl.getInheritedPage("SetUp", wikiPage);
                    if (setup != null) {
                        WikiPagePath setupPath =
                        wikiPage.getPageCrawler().getFullPath(setup);
                        String setupPathName = PathParser.render(setupPath);
                        buffer.append("!include -setup .")
                            .append(setupPathName)
                            .append("\n");
                    }
                }
            buffer.append(pageData.getContent());
            if (pageData.hasAttribute("Test")) {
                WikiPage teardown =
                PageCrawlerImpl.getInheritedPage("TearDown", wikiPage);
                
                if (teardown != null) {
                    WikiPagePath tearDownPath =
                    wikiPage.getPageCrawler().getFullPath(teardown);
                    String tearDownPathName = PathParser.render(tearDownPath);
                    buffer.append("\n")
                        .append("!include -teardown .")
                        .append(tearDownPathName)
                        .append("\n");
                }
         }   
            if (includeSuiteSetup) {
                WikiPage suiteTeardown = PageCrawlerImpl.getInheritedPage(SuiteResponder.SUITE_TEARDOWN_NAME, wikiPage);
                if (suiteTeardown != null) {
                    WikiPagePath pagePath = suiteTeardown.getPageCrawler().getFullPath (suiteTeardown);
                    String pagePathName = PathParser.render(pagePath);
                    buffer.append("!include -teardown .")
                    .append(pagePathName)
                    .append("\n");
                }
            }
        
            pageData.setContent(buffer.toString());
            return pageData.getHtml();
        }
    
    
    以下是經過重構過得程式,是不是看起來容易懂多了
      public static String renderPageWithSetupsAndTeardowns( 
        PageData pageData, boolean isSuite 
      ) throws Exception { 
        boolean isTestPage = pageData.hasAttribute("Test"); 
        if (isTestPage) { 
          WikiPage testPage = pageData.getWikiPage(); 
          StringBuffer newPageContent = new StringBuffer(); 
          includeSetupPages(testPage, newPageContent, isSuite); 
          newPageContent.append(pageData.getContent()); 
          includeTeardownPages(testPage, newPageContent, isSuite); 
          pageData.setContent(newPageContent.toString()); 
        } 
        return pageData.getHtml(); 
      } 
    
  • Small!

    • Function should be small
    • Function以20行為最佳
      • 之前的例子再進一步重構
      • public static String renderPageWithSetupsAndTeardowns(PageData pageData,
            boolean isSuite) throws Exception {
            if (isTestPage(pageData)) {
                includeSetupAndTeardownPages(pageData, isSuite);
            }
            return pageData.getHtml();
        }
        
      • if, else, while其中的block最好只有一行,而那一行是一個function call

  • Do One Thing

    • 第一個範例會讓人看不懂是因為他做太多事情了
    • Function should do one thing. They should do it well. They should do it only.
    • 當寫完function後,要再檢查看看是否還能把其中內容再拆成另一個function

  • One Level of Abstraction per Function

    • 函式中的statments都要再同一個抽象層級上
    • 第一個範例中的getHtml()就是屬於較高層級的概念,append則是低層級的概念
    • Reading code from top to bottom
      • 就像讀報紙一樣

  • Switch Statements

    • 我們很難讓switch精簡短小
    • 使用abstract factory和多型來處理switch

  • Use Descriptive Name

    • You know you are working on clean code when each routine you read turns out to be pretty much what you expected."
    • 不要害怕使用long name,SetupTeardownIncluder.render會比testableHtml要來的好

  • Function Arguments

    • 最理想的function是沒有參數,其次是一個參數,再來是兩個參數
    • 除非是特殊理由,否則不要使用三個以上的參數
    • 盡量避免使用output arguments,因為大多數人預期function由參數輸入並且由return回傳,output arguments會讓人想比較久
      • boolean fileExists("MyFile") 透過參數來問一個問題
      • InputStream fileOpen("MyFile") 操作一個參數並轉換成InputStream回傳
    • 還有一種形式是event,void passwordAttemptFailedNtimes(int attempts)
      • 此形式沒有return value,通常用於設定系統參數
      • 小心為他命名,務必讓讀者知道這是一個event
    • Flag Arguments
      • Flag arguments are ugly!
      • 違反Do One Thing的原則,盡量拆成兩個functions
    • 兩個參數的function
      • 比一個參數來的難懂
      • assertEquals(expected, actual),很容易搞混前後參數的次序,expected在前只是一種不成文的規定
      • 盡量拆解成一個參數的function,ex: writeField(outputStream, name)可改成outputStream.writeField(name)
    • 三個參數以上的function
      • 三個以上的參數非常難懂
      • 參數有三個以上,通常代表你需要把其中參數封裝成class,ex: x, y封裝成Point
      • 盡量拆解成一個參數的function,ex: writeField(outputStream, name)可改成outputStream.writeField(name)

  • Have No Side Effects

    • 執行以下這個function會有隱藏的side effect
    • public class UserValidator {
        private Cryptographer cryptographer;
        public boolean checkPassword(String username, String password) {
          User user = UserGateway.findByName(username);
          if (user != User.NULL) {
             String codedPhrase = user.getPhraseEncodedByPassword();
             String phrase = cryptographer.decrypt(codedPhrase, password);
             if ("Valid Password".equals(phrase)) {
               Session.initialize();
               return true;
             }
          }
          return false;
        }
      }
      
    • 函式名稱並沒有提到會去call Session.initialize(),因此有一個隱藏的side effect
    • 一定要做的話要重新命名函式為checkPasswordAndInitializeSession (但是這違反Do One Thing)

  • Command Query Separation

    • 一個函式應該專門回答問題或是專心做某件事,而不是兩者都做
    • public boolean set(String attribute, String value),if (set("username", "unclebob"))...
      • set完參數後return是否成功,容易讓讀者搞混
    • 拆成兩個會比較好
    • if (attributeExist("username")) {
        setAttribute("username", "unclebob");
      }
      

  • Prefer Exceptions to Returning Error Codes

    • if (deletePage(page) == E_OK) 使用Exceptions來處理這種returning error codes

  • Don't Repeat Yourself

    • 不要在函式中複製貼上重複的code

  • Structured Programming

    • 在小函式中可以使用break和continue
    • 避免使用goto

    2012年1月4日 星期三

    First-class function

    如果一個程式語言的function是First-class object,我們就會說這個程式語言有First-class function
    所以什麽是First-class object呢? 簡單的說就是function可以當作物件來使用
    因此你可以把一個function當成參數傳給另一個function
    可以在funciton中return一個function
    也可以assign function給一個變數

    有許多程式語言有Frist-class function,比如說: Scheme, Haskell, JavaScript, Python......
    以下我們以Python為例來說明Frist-class function的一些特性
    • Assign to a variable
    def average(number):
        return sum(number) / len(number)
    
    a = average
    print a([1, 2, 3, 4, 5])
    
    Output: 3
    
    以上這個範例會印出3,a其實就是average的alias
    • Put the function in a list
    def area(r):
        return PI * r * r
    
    def circumference(r):
        return 2 * PI * r
    
    funcs = [area, circumference]
    • Pass the function into a function
    def area(r):
        return PI * r * r
    
    def foo(func, value):
        return func(value)
    
    print foo(area, 1.0)
    
    Output: 3.14159