zoukankan      html  css  js  c++  java
  • (转载)閱讀他人的程式碼(5)找到程式入口,再由上而下抽絲剝繭

    根據需要決定展開的層數,或展開特定節點,並記錄樹狀結構,然後適度忽略不需要了解的細節─這是一個很重要的態度。因為你不會一次就需要所有的細節,閱讀都是有目的的,每次的閱讀也許都在探索程式中不同的區域。

    探索系統架構的第一步,就是找到程式的入口點。找到入口點後,多半採取由上而下(Top-Down)的方式,由最外層的結構,一層一層逐漸探索越來越多的細節。

    我們的開發團隊曾針對Winamp的iPod plug-in進行閱讀及探索,不僅找到入口點,也找出、並理解它最根本的基礎架構。從這個入口點,可以往下再展開一層,分別找到三個重要的組成及其意義:
    ● init():初始化動作
    ● quit():終止化動作
    ● PluginMessageProc():以訊息的方式處理程式所必須處理的各種事件

    展開的同時,隨手記錄樹狀結構
    當我們從一個入口點找到三個分支後,可以順著每個分支再展開一層,所以分別繼續閱讀init、quit、以及PluginMessageProc的內容,並試著再展開一層。閱讀的同時,你可以在文件中試著記錄展開的樹狀結構。

    ● init():初始化動作
    ● itunesdb_init_cc():建立存取iTunes database的同步物件
    ● 初始化資料結構
    ● 初始化GUI元素
    ● 載入設定
    ● 建立log檔
    ● autoDetectIpod():偵測iPod插入的執行緒
    ● quit():終止化動作
    ● itunesdb_del_cc():終止存取iTunes database的同步物件
    ● 關閉log檔
    ● 終止化GUI元素
    ● PluginMessageProc():以訊息的方式處理程式所必須面臨的各種事件
    ● 執行所連接之iPod的MessageProc()
    這部分必須要留意幾個重點。首先,應該一邊閱讀,一邊記錄文件。因為人的記憶力通常有限,對於陌生的事物更是容易遺忘,因此邊閱讀邊記錄,是很好的輔助。

    再者,因為我們採取由上而下的方式,從一個點再分支出去成為多個點,因此,通常也會以樹狀的方式記錄。除此之外,每次只試著往下探索一層。從init()來看你便會明白。以下試著摘要init()的內容:

    int init() {
    itunesdb_init_cc();
    currentiPod=NULL;
    iPods = new C_ItemList;
    …略
    conf_file=(char*)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_GETINIFILE);
    m_treeview = GetDlgItem(plugin.hwnd LibraryParent,0x3fd);
    //this number is actually magic :)
    …略
    g_detectAll = GetPrivateProfileInt("ml_ipod", "detectAll",0,conf_file)!=0;
    …略
    g_log=GetPrivateProfileInt("ml_ipod","log",0,conf_file)!=0;
    …略
    g_logfile=fopen(g_logfilepath,"a");
    …略
    autoDetectIpod();
    return 0;
    }


    因為我們只試著多探索一層,而目的是希望發掘出下一層的子動作。所以在init()中看到像「itunesdb_init_cc();」這樣的函式呼叫動作時,我們知道它是在init()之下的一個獨立子動作,所以可以直接將它列入。但是當看到如下的程式行:

    currentiPod=NULL;
    iPods = new C_ItemList;

    我們並不會將它視為init()下的一個獨立的子動作。因為好幾行程式,才構成一個具有獨立抽象意義的子動作。例如以上這兩行構成了一個獨立的抽象意義,也就是初始化所需的資料結構。

    理論上,原來的程式撰寫者,有可能撰寫一個叫做init_data_structure()的函式,包含這兩行程式碼。這樣做可讀性更高,然而基於種種理由,原作者並沒有這麼做。身為閱讀者,必須自行解讀,將這幾行合併成單一個子動作,並賦予它一個獨立的意義──初始化資料結構。

    無法望文生義的函式,先試著預看一層
    對於某些不明作用的函式叫用,不是望其文便能生其義的。當我們看到「itunesdb_init_cc()」這個名稱時,我們或許能從「itunesdb_init」的字眼意識到這個函式和iPod所採用的iTunes database的初始化有關,但「cc」卻實在令人費解。為了理解這一層某個子動作的真實意義,有時免不了要往前多看一層。

    原來它是用來初始化同步化機制用的物件。作用在於這程式一定是用了某個內部的資料結構來儲存iTunes database,而這資料結構有可能被多執行緒存取,所以必須以同步物件(此處是Windows的Critical Section)加以保護。

    所以說,當我們試著以樹狀的方式,逐一展開每個動作的子動作時,有時必須多看一層,才能真正了解子動作的意義。因為有了這樣的動作,我們可以在展開樹狀結構中,為itunesdb_init_cc()附上補充說明:建立存取itunes database的同步物件。這麼一來,當我們在檢視自己所寫下的樹狀結構時,就能輕易一目了然的理解每個子動作的真正作用。

    根據需要了解的粒度,決定展開的層數
    我們究竟需要展開多少層呢?這個問題和閱讀程式碼時所需的「粒度(Granularity)」有關。如果我們只是需要概括性的了解,那麼也許展開兩層或三層,就能夠對程式有基礎的認識。倘若需要更深入的了解,就會需要展開更多的層次才行。

    有時候,你並不是一視同仁地針對每個動作,都展開到相同深度的層次。也許,你會基於特殊的需求,專門針對特定的動作展開至深層。例如,我們閱讀Winamp iPod plug-in的程式目錄,其實是想從中了解究竟應該如何存取iPod上的iTunes DB,使我們能夠將MP3歌曲或播放清單加至此DB中,並於iPod中播放。

    當我們層層探索與分解之後,找到了parseIpodDb(),從函式名稱判斷它是我們想要的。因為它代表的正是parse iPod DB,正是我們此次閱讀的重點,也就達成閱讀這程式碼的目的。

    我們強調一種不同的做法:在閱讀程式碼時,多半採取由上而下的方式;而本文建議了一種記錄閱讀的方式,就是試著記錄探索追蹤時層層展開的樹狀結構。你可以視自己需要,了解的深入程度,再決定要展開的層數。你更可以依據特殊的需要,只展開某個特定的節點,以探索特定的細目。

    適度地忽略不需要了解的細節,是一個很重要的態度,因為你不會一次就需要所有的細節,閱讀都是有目的的。每次的閱讀也許都在探索程式中不同的區域;而每次探索時,你都可以增補樹狀結構中的某個子結構。漸漸地,你就會對這個程式更加的了解。

    作者簡介:
    王建興
    清華大學資訊工程系的博士研究生,研究興趣包括電腦網路、點對點網路、分散式網路管理、以及行動式代理人,專長則是Internet應用系統的開發。曾參與過的開發專案性質十分廣泛而且不同,從ERP、PC Game到P2P網路電話都在他的涉獵範圍之內。

    转载于:http://www.ithome.com.tw/node/48168(可能需要翻墙)

  • 相关阅读:
    hive报错 java.sql.SQLException: No suitable driver found for jdbc:hive://localhost:10000/default
    使用Beeline连接Hive
    hive报错 root is not allowed to impersonate root (state=08S01,code=0)
    hive报错 Could not open client transport with JDBC Uri: jdbc:hive2://node01:10000/default:java.net.ConnectException refused
    excel快速删除空值单元格,数据上移
    FineBI 图表选择
    数据库连接池大小设置?
    工作中有待留❤️积累的一些经验
    内存包括随机存储器(RAM),只读存储器(ROM),以及高速缓存(CACHE)。RAM最重要
    我自己ood的复习思路:抄
  • 原文地址:https://www.cnblogs.com/midhillzhou/p/6013034.html
Copyright © 2011-2022 走看看