Pretty工具類:讓軟件開發調試與單元測試更happy!

    在軟件開發調試過程中,經常會去查看某一對象的取值。但類之間複雜的層次關係,再加上數組(鏈表)、映射(字典)等多種數據結構,讓我們難以一目瞭然。本文介紹的Pretty工具類,以縮進的方式突出類之間的層次關係,並且將對象一層層的整個結構pretty地打印出來!

    在編寫單元測試時,經常會去比較某一對象是否符合預先的期望值。但對於一個複雜類的對象,這種單元測試並不好寫,容易片面化、複雜化。Pretty工具類,既能夠完整的檢測複雜類的對象,而且可讀性好,便於理解代碼。當檢測出與期望結構不匹配時,不僅可以輸出Diff信息,還能提醒用戶是否需要自動更新case,簡單易用。

    本文實現了3種語言版本的Pretty工具類:Java版,Python版,Groovy版。這裏對Java版的Pretty做了重點介紹,而其它版本只是簡單帶過,因爲實現的原理都是一樣的,只是換成不同語言而已。最後,對於同樣廣泛應用的C++,本文雖然沒有給出具體實現,但也提供了一個設計思路,有興趣的朋友可以試一試。

1. Pretty之Java版


1.1 調試中的問題

    當我們在調試程序的時候,經常會查看某一變量的值。一般來說,有兩種方法被經常用到:

1.     用調試器,如Eclipse  Debug,或者gdb/pdb。

2.     用print函數或者logger,直接將變量值打印出來。

    這兩種辦法都有缺點,調試器需要一層層展開看,而且如果杯具碰到鏈表結構或者哈希表的時候,就不太容易看明白了。而print函數其實只是toString方法的返回值,取決於toString函數的實現,其實並不可靠。

    可能有聰明的讀者會想到,那我們在定義類的時候,都override一下toString方法,讓它可讀,而不是Object類的缺省實現(JVM中的地址)。

    這是一個很天真的想法:

1.     首先,不是所有的類都能夠由我們控制,如Java類庫,第三方庫,其他開發團隊的代碼,等等。

2.     類的toString方法可能有它的業務價值,而不只是爲了方便調試。

3.     額外工作量:這不是必須的,若是其中要求每個類都去override toString方法,會增加很多沒有必要的工作量,產生很多不必要的代碼,反而會增大維護代碼的工作量,甚至引起bug。而且,當類每次增加/修改/刪除成員變量時,都要去修改toString方法,否則print出來的信息也就不可靠了,但這是很難保證的。

1.2 單元測試中的問題

    有下面一個類A,聚合了類B,而B又聚合類C,如下代碼:

  class A {
    private int id;
    private File path;
    private Integer[] array;
    private List<String> list;
    private B b; 
    // ...
  }
  
  class B {
    private String desc;
    private Map<String, Date> map;
    private C c = new C();
    // ...
  }
  
  class C {
    private double v1;
    private BigDecimal v2;
    // ...
  }

    現在我們先測試一下類 A 的對象 a 是不是所期待的,一般容易想到下面幾個方法:

1. 把所有成員變量都 get 出來比較:

assertEquals(xxx, a.getId());
assertEquals(xxx, a.getPath());
...
assertEquals(xxx, a.getB().getDesc());
...
assertEquals(xxx, a.getB().getC().getV1());
...

    這種辦法的問題顯而易見:如果不小心漏了一個重要成員變量的get,那測試就不夠全面了。而且並不是所有成員變量都有get方法,需不需要get方法得看具體需要,而不能只爲unit test專門提供。而且,像這麼簡單的類,都需要這麼多行assertEqual,如果有更多的成員變量或者有很深的聚合層次,那將無法想象。如果A類的結夠在做個調整,那改動的地方就很多了。這樣的unit test維護成本之高可想而知,還有誰有動力寫unit test,因爲那是在給自己找麻煩!而且,不是所有的代碼我們都能控制的,比如第三方庫。

2. 爲A類實現equals方法,那assertEquals就只有一個了:

A expected = new A();
expected.setId(xxx);
expected.setPath(xxx);
...
expected.setB(new B());
expected.getB().setDesc(xxx);
...
expected.getB().setC(new C());
expected.getB().getC().setV1(xxx);
...
assertEquals(expected, a);

    雖然assertEquals只有一個,但爲了建立一個期待的expected作爲標尺來比較,需要爲提供大量的set方法。這麼多set方法帶來的問題其實並不比那麼多get少。

    而且,爲A類實現equals方法也是有風險的,因爲equals方法本身也需要測試(只要是人寫的代碼本質上都需要測試!),也需要時間成本。很多類其實沒必要去override equals方法。寫代碼就得維護,沒必要寫的代碼堅決不寫,否則維護量更多。同樣的,不是所有的代碼都能控制的。

1.3 使用Pretty

    Pretty類可以很pretty的解決以上調試和單元測試中的問題。在給出Pretty類之前,先從使用者的角度看看她的pretty:

package org.wenzhe.jvlib.debug.test;

import static org.junit.Assert.*;

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.junit.Test;
import org.wenzhe.jvlib.debug.Pretty;

/**
 * @author [email protected]
 *
 */
public class PrettyTest {
  private static class A {
    private int id = 100;
    private File path = new File("/home/wenzhe/code/pretty");
    private Integer[] array = {86, 755, 1234, 5678};
    private List<String> list = Arrays.asList("My", "name", "is", "Wenzhe");
    private B b = new B("This is my Pretty Test"); 
  }
  
  private static class B {
    private String desc;
    private Map<String, Date> map = new HashMap<String, Date>();
    private C c = new C();

    public B(String desc) {
      this.desc = desc;
      SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
      try {
        map.put("Today", dateFormat.parse("2013-06-15"));
        map.put("Earth Doomsday", dateFormat.parse("2012-12-21"));
      } catch (ParseException e) {
        throw new RuntimeException(e);
      }
    }
  }
  
  private static class C {
    private double v1 = 3.14;
    private BigDecimal v2 = new BigDecimal(
        "3.141592653589793238462643383279502884197169399");
  }

  @Test
  public void test1() throws IOException {
    A a = new A();
    assertTrue(Pretty.equalsGolden("test1", a));
  }

  public static void main(String[] args) throws IOException {
    Pretty.setDebugMode(true);
    PrettyTest test = new PrettyTest();
    test.test1();
  }
}

1.3.1 Pretty結構

    這是一個帶有main方法的單元測試類。先撇開單元測試,我們把它當成一個普通java文件來運行(即從main方法開始運行),在屏幕上會打印出對象 a 的pretty結構:

org.wenzhe.jvlib.debug.test.PrettyTest$A {
  array : [86, 755, 1234, 5678]
  b : org.wenzhe.jvlib.debug.test.PrettyTest$B {
    c : org.wenzhe.jvlib.debug.test.PrettyTest$C {
      v1 : 3.14
      v2 : 3.141592653589793238462643383279502884197169399
    }
    desc : This is my Pretty Test
    map : {Earth Doomsday=Fri Dec 21 00:00:00 PST 2012, Today=Sat Jun 15 00:00:00 PDT 2013}
  }
  id : 100
  list : [My, name, is, Wenzhe]
  path : pretty
}

    根據pretty結構的縮進,可以很容易看出,對象a的類是: org.wenzhe.jvlib.debug.test.PrettyTest類的內部類A,其成員array是一個數組,值爲[86, 755, 1234, 5678]。另一個成員 b 是類 (PrettyTest的內部類B)的對象,b 裏面的成員變量c 是類(PrettyTest內部類C)的對象……根據縮進,各種成員變量及其嵌套聚合類的對象也都輕易可見。這在開發調試過程中非常好用!

    如果以單元測試的方式運行,屏幕上沒有任何輸出(No news is Good news),JUnit View中出現大家喜愛的綠色條,祝賀你表測試通過了。(一般對於unittest來說,正確的時候是沒輸出信息的。)

    那麼程序怎麼知道對象a是期望的呢?注意到第61行,Pretty.equalsGolden("test1", a);  對象a實際上是跟一個名字爲test1的golden文件做了比較。這個golden文件的所在的目錄爲: ${project_root}/src/test/resources/golden/pretty/,這是pretty工具的一個convention,當然也可以改成別的目錄,但我不推薦改,很多時候遵從“約定優於配置”的原則總是更好的。打開test1文件,你會發現這也是一個pretty結構,跟之前屏幕上輸出的完全一樣。

    你可能奇怪爲什麼作爲普通java類運行屏幕上會打印,而作爲unit test卻不會打印呢?其實區別並不在於用哪種運行方式,唯一的區別在於是否選擇了Pretty類的debug模式。(一般來說,unit test下不啓動debug模式,而在開發調試過程中啓動)。注意到main函數剛開始的時候(第65行),debug mode設置爲true,當Pretty工具要得到對象a的pretty結構時,會將它打印出來,方便調試,省得在代碼裏面加入print函數的麻煩。debug mode缺省是關的,所以unit test就沒有打印出來了。(有興趣可以閱讀後面的源代碼)。

1.3.2 Pretty Diff

    如果unit test測到對象a與golden文件不同,那會怎樣?假如有個大老粗不小心把C類中的成員變量v1刪除了,又不小心增加了成員變量v3(取值爲true),更是不小心把A類的成員變量list裏面insert了一個“NOT”,不管是不是在Pretty的debug模式,屏幕上都會輸出:

Diff from Expected to Actual: 
-:       v1 : 3.14
+:       v3 : true
<:   list : [My, name, is, Wenzhe]
>:   list : [My, name, is, NOT, Wenzhe]

    Pretty工具的錯誤輸出,夠pretty吧,大老粗幹了哪些壞事這裏一目瞭然。

1.3.3 Pretty Golden

    如果大老粗是故意這麼修改的(背後有大老闆支持,用軟件行業的語言講就是“需求變了”),那麼golden文件也就過時了,需要更新才能讓unit test通過。

    需要手動更改golden文件嗎?我可不幹!因爲Pretty讓我越來越懶了。

    懶人都喜歡Pretty,因爲Pretty提供了自動更新golden文件的功能。這時候你開啓Pretty的debug模式,運行,屏幕上除了輸出對象a的pretty結構和Diff信息之外,Pretty還會問你“Overwrite (Y/N)? ”,回答Y即可自動更新golden文件test1。

    有了Pretty,你永遠不需要手動寫golden文件:當golden不存在時,Pretty會幫你創建;當golden存在但有Diff時會提醒你是否需要更新。

1.4 Pretty原理及源碼

    也許你已經迫不及待地想知道Pretty類是怎麼實現的,原理其實也簡單,就是通過Java的“反射”機制,把類的成員變量拿出來,放進一個Map裏,key爲成員變量名,value爲成員變量的值,然後遞歸地輸出到一個具有縮進層次的代表pretty結構的字符串裏。這是一個既美麗又好用的字符串,在debug模式下打印到標準輸出,在unit test下就是與golden文件進行字符串比較,從而避免了做對象比較的麻煩,同時golden文件的pretty結構記錄了期待對象完整的層層信息,有助於理解代碼,^_^。

package org.wenzhe.jvlib.debug;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.wenzhe.jvlib.file.FileUtil;

/**
 * @author [email protected]
 *
 */
public class Pretty {

  private static final String TAB = "  ";

  private static boolean debugMode = false;
  private static boolean showFileAbsPath = false;

  public static void setDebugMode(boolean toDebug) {
    debugMode = toDebug;
    Golden.setDebugMode(debugMode);
  }
  
  public static void setShowFileAbsPath(boolean toShowFileAbsPath) {
    showFileAbsPath = toShowFileAbsPath;
  }

  private static Map<String, Object> obj2map(Object o) {
    Map<String, Object> props = new TreeMap<String, Object>();
    Class<?> c = o.getClass();
    for (Field field : c.getDeclaredFields()) {
      String name = field.getName();
      Object value = null;
      boolean originalAccessible = field.isAccessible();
      if (!originalAccessible) {
        field.setAccessible(true);
      }
      try {
        value = field.get(o);
      } catch (IllegalArgumentException e) {
        throw new RuntimeException("Should not reach!", e);
      } catch (IllegalAccessException e) {
        throw new RuntimeException("Should not reach!", e);
      } finally {
        if (!originalAccessible) {
          field.setAccessible(false);
        }
      }
      props.put(name, value);
    }
    return props;
  }

  public static void println(Object obj, int level) {
    System.out.println(str(obj, level));
  }
  
  public static String str(Object obj, int level) {
    return str(obj, level, debugMode);
  }

  public static String str(Object obj, int level, boolean debugMode) {
    String result = str(obj, 0, level);
    if (debugMode) {
      System.out.println(result);
    }
    return result;
  }

  @SuppressWarnings("unchecked")
  private static String str(Object obj, int tabCnt, int level) {
    if (obj == null) {
      return "";
    }
    else if (tabCnt > level ||
        obj instanceof String || 
        obj instanceof BigDecimal || 
        obj instanceof BigInteger ||
        obj instanceof Integer || 
        obj instanceof Short ||
        obj instanceof Long ||
        obj instanceof Float ||
        obj instanceof Double ||
        obj instanceof Boolean ||
        obj instanceof Class ||
        obj instanceof Date
        ) {
      return obj.toString();
    }
    else if (obj instanceof File) {
      File file = (File)obj;
      if (showFileAbsPath) {
        return FileUtil.unixPath(file.getAbsoluteFile());
      }
      else {
        return file.getName();
      }
    }

    else if (obj instanceof Iterable) {
      List<String> results = new ArrayList<String>();
      for (Object o : (Iterable<?>)obj) {
        results.add(str(o, tabCnt + 1, level));
      }
      return results.toString();
    }
    else if (obj instanceof Object[]) {
      List<String> results = new ArrayList<String>();
      for (Object o : (Object[])obj) {
        results.add(str(o, tabCnt + 1, level));
      }
      return results.toString();
    }
    else if (obj instanceof Map) {
      Map<String, String> results = new TreeMap<String, String>();
      for (Map.Entry<Object, Object> entry : ((Map<Object, Object>)obj).entrySet()) {
        String key = str(entry.getKey(), tabCnt + 1, level);
        String value = str(entry.getValue(), tabCnt + 1, level);
        results.put(key, value);
      }
      return results.toString();
    }
    else {
      Map<String, Object> m = obj2map(obj);
      StringBuilder sb = new StringBuilder();
      sb.append(obj.getClass().getName() + " {\n");

      String nTabs = tabs(tabCnt + 1);
      for (Map.Entry<String, Object> entry : m.entrySet()) {
        sb.append(nTabs);
        sb.append(entry.getKey());
        sb.append(" : ");
        sb.append(str(entry.getValue(), tabCnt + 1, level));
        sb.append("\n");
      }
      sb.append(tabs(tabCnt));

      sb.append("}");
      return sb.toString();
    }
  }

  private static String tabs(int count) {
    StringBuilder sb = new StringBuilder();
    while (count-- > 0) {
      sb.append(TAB);
    }
    return sb.toString();
  }
  
  public static boolean equalsGolden(String goldenFileName, Object obj) throws IOException {
    return equalsGolden(goldenFileName, obj, 5);
  }

  public static boolean equalsGolden(String goldenFileName, Object obj, int level) throws IOException {
    File goldenFile = new File("src/test/resources/golden/pretty", goldenFileName);
    return equalsGolden(goldenFile, obj, level);
  }
  
  public static boolean equalsGolden(File goldenFile, Object obj, int level) throws IOException {
    String actual = str(obj, level, false).trim();
    return Golden.equals(goldenFile, actual);
  }
}

    在調試過程中,Pretty的str方法和println方法是很常用的;而在unit test中,equalsGolden方法更加方便。

1.5 Pretty姐妹篇:Golden原理及源碼

    Pretty類用到了另一個相當實用的工具類:Golden,是Pretty的好姐妹,如果golden文件不存在則幫你創建,如果存在了則幫你把字符串跟golden文件做比較,一旦發現差異,則將差異部分打印出來。在Golden類的調試模式下(debugMode=true)還會提示你是否需要overwirte你的golden文件。這是很實用的功能,試想一下如果有上千個golden文件,維護的工作量是很大的。需求變了,代碼結構也變了,原先的golden不再正確時就需要更新。要是每次都得手動去文件裏查找哪些不同,手動去修改golden文件,那也是相當麻煩的事。Golden類可以給你“一鍵搞定”的成就感!

package org.wenzhe.jvlib.debug;

import java.io.File;
import java.io.IOException;

import org.wenzhe.jvlib.diff.DiffUtil;

import com.google.common.base.Charsets;
import com.google.common.io.Files;

/**
 * @author [email protected]
 *
 */
public class Golden {
  
  private static boolean debugMode = false;
  
  public static void setDebugMode(boolean toDebug) {
    debugMode = toDebug;
  }

  public static boolean equals(String goldenFileName, String actual) throws IOException {
    File goldenFile = new File("src/test/resources/golden", goldenFileName);
    return equals(goldenFile, actual);
  }
  
  public static boolean equals(File goldenFile, String actual) throws IOException {
    if (debugMode) {
      System.out.println(actual);
    }
    goldenFile = goldenFile.getAbsoluteFile();
    if (!goldenFile.isFile()) {
      System.out.println("Generate golden file: " + goldenFile);
      Files.createParentDirs(goldenFile);
      Files.write(actual, goldenFile, Charsets.UTF_8);
      return true;
    }
    String expected = Files.toString(goldenFile, Charsets.UTF_8);
    if (actual.equals(expected)) {
      return true;
    } else {
      // need 3'rd party: diffutils {
      System.out.println("Diff from Expected to Actual: ");
      System.out.println(DiffUtil.diff(expected, actual));
      if (debugMode) {
        System.out.print("Overwrite (Y/N)? ");
        char in = (char)System.in.read();
        if (in == 'Y' || in == 'y') {
          Files.write(actual, goldenFile, Charsets.UTF_8);
          return true;
        }
      }
      // }
      return false;
    }
  }
}

2. Pretty之Python版

Python的實現方法非常簡單,自帶的pprint方法就可以實現pretty print,因此要做到主要是將object轉換成dict(即Java裏的Map),而Python自帶的vars函數返回的就是成員變量的dict。源碼如下:

# author: [email protected]
import pprint
from StringIO import StringIO

def obj2map(o):
    """ if o doesn't have __dict__, not return map """
    if hasattr(o, "__dict__"):
        m = vars(o)
        return obj2map(m)
    elif type(o) == dict:
        m = {}
        for k,v in o.items():
            key = obj2map(k)
            if not key.__hash__:
                key = str(key)
            m[key] = obj2map(v)
        return m
    elif type(o) == list:
        arr = []
        for item in o:
            arr.append(obj2map(item))
        return arr
    elif type(o) == tuple:
        return tuple(obj2map(list(o)))
    else:
        return o
    
def printObj(o, stream=None):
    """Pretty-print a mapped Python object to a stream [default is sys.stdout]."""
    pprint.pprint(obj2map(o), stream)
    
def obj2str(o):
    s = StringIO()
    printObj(o, s)
    return s.getvalue()

    由於Python的動態腳本語言特性,我們可以在運行時導入Pretty,然後打印感興趣的對象。下面是Pretty在pdb調試中的例子。

pdb> import Pretty
pdb> Pretty.printObj(xxx)

    Python版的Pretty,輸出結果也是同樣pretty,請看下面的unit test文件,特別是複雜類A的對象a所對應的pretty結構,即字符串expectedStrA

# author: [email protected]
import unittest
import Pretty

class A:
    def __init__(self):
        self.a1 = "a"
        self.a2 = 2
        self.b = B()
        self.c = [C(1), C(2)]
        
class B:
    def __init__(self):
        self.bm = {3: C(3), "4": C(4), C(7):C(8)}
        self.bt = (C(5), C(6))

class C:
    def __init__(self, c):
        self.c = c
        self.cs = str(c)
        
expectedMapA = \
{'a1': 'a',
 'a2': 2,
 'b': {'bm': {3: {'c': 3, 'cs': '3'},
              '4': {'c': 4, 'cs': '4'},
              "{'cs': '7', 'c': 7}": {'c': 8, 'cs': '8'}},
       'bt': ({'c': 5, 'cs': '5'}, {'c': 6, 'cs': '6'})},
 'c': [{'c': 1, 'cs': '1'}, {'c': 2, 'cs': '2'}]}

expectedStrA = """
{'a1': 'a',
 'a2': 2,
 'b': {'bm': {3: {'c': 3, 'cs': '3'},
              '4': {'c': 4, 'cs': '4'},
              "{'cs': '7', 'c': 7}": {'c': 8, 'cs': '8'}},
       'bt': ({'c': 5, 'cs': '5'}, {'c': 6, 'cs': '6'})},
 'c': [{'c': 1, 'cs': '1'}, {'c': 2, 'cs': '2'}]}
"""
        
class Test(unittest.TestCase):
    def setUp(self):
        self.a = A()
    
    def testObj2map(self):
        m = Pretty.obj2map(self.a)
        self.assertEqual(expectedMapA, m)
        self.assertEqual(type(B()), type(self.a.b))
        
    def testObj2Str(self):
        s = Pretty.obj2str(self.a)
        self.assertEqual(expectedStrA.strip(), s.strip())
        
        Pretty.printObj(self.a)

if __name__ == "__main__":
    unittest.main()



3. Pretty之Groovy版

    Groovy是語法簡化、但卻功能擴展的Java,思路是一樣的,只是代碼寫起來簡單一些(比如反射、格式化等)。源碼如下:

package org.wenzhe.gvlib

/**
 * Pretty print an object detailed to string, console
 * 
 * @author [email protected]
 *
 */
class Pretty {
  private static final String TAB = " " * 2
  
  static String str(obj) {
    return str(obj, false)
  }
  static String str(obj, boolean recursive) {
    return strLevel(obj, (recursive ? 1 : 0))
  }
  private static String strLevel(obj, int tabLevel) {
    if (obj == null ||
        obj instanceof String || 
        obj instanceof BigDecimal || 
        obj instanceof BigInteger ||
        obj instanceof Integer || 
        obj instanceof Short ||
        obj instanceof Long ||
        obj instanceof Float ||
        obj instanceof Double ||
        obj instanceof Boolean ||
        obj instanceof Class
        ) {
      return obj.toString()
    }
    if (  obj instanceof List || 
        obj instanceof Object[] ||
        obj instanceof Set
        ) {
      List<String> prettyList = obj.collect {
        if (tabLevel <= 0) {
          return str(it)
        } else {
          return strLevel(it, tabLevel + 1)
        }
        
      }
      return prettyList.toString()
    }
    if (obj instanceof Map) {
      if (!obj) {
        return obj.toString()
      }
      List<String> list = obj.collect() { key, val ->
        if (tabLevel <= 0) {
          return str(key) + ' : ' + str(val)
        } else {
          return str(key) + ' : ' + strLevel(val, tabLevel + 1)
        }
      }
      return str(list)
    }
    
    Map<String, Object> props = obj.getProperties()
    
    if (tabLevel <= 0) {
      return props.inject(obj.class.name + ' {\n') { buf, entry ->
        if (entry.key == "class") {
          return buf;
        } else {
          return buf + TAB + "$entry.key : $entry.value\n"
        }
      } + "}"
    } else {
      return props.inject(obj.class.name + ' {\n') { buf, entry ->
        if (entry.key == "class") {
          return buf;
        } else {
          return buf + strFormat(entry.key, entry.value, tabLevel)
        }
      } + TAB * (tabLevel - 1) + "}"
    }
  }
  
  private static String strFormat(String key, Object value, int tabLevel) {
    String s = strLevel(value, tabLevel + 1)
    return TAB * tabLevel + "$key : $s\n";
  }
  
  static String listMethodObjs(obj) {
    return Pretty.str(obj.metaClass.methods)
  }
  
  static String listMethodDescs(obj) {
    List<String> methods = obj.metaClass.methods.cachedMethod*.toString()
    return formatMethodList(obj, methods)
  }
  
  static String listMethodNames(obj) {
    List<String> methods = obj.metaClass.methods.cachedMethod*.name.sort().unique()
    return formatMethodList(obj, methods)
  }
  
  static private String formatMethodList(obj, List<String> methods) {
    return """${obj.class.name} {
    ${methods.join("\n    ")}
}"""
  }
}


4. Pretty之C++設計思路

    由於C++沒有“反射”機制,要想獲取類的所有私有(或公有)成員變量的名字與類型並不容易。但思路還是有的,比如可以通過分析C++類的源代碼來獲得,可以藉助第三方庫,如Clang來實現。Clang由Apple開發,BSD開源授權,支持C,C++,Object C,Object C++等編程語言,能夠對源代碼進行詞法和語意分析,結果爲抽象語法樹。通過抽象語法樹,我們可以模仿類似與Java中“反射”機制,來得到類的成員信息(名字,類型,取值等)。只是一個思路,有興趣的朋友不妨一試。


 ---------------------- 本博客所有內容均爲原創,轉載請註明作者和出處 -----------------------

 作者:劉文哲

 聯繫方式:[email protected]

 博客:http://blog.csdn.net/liuwenzhe2008

-------------------------------------------------------------------------------------------------------------






發佈了26 篇原創文章 · 獲贊 8 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章